feat: toggle pin status

pull/198/head
金伟强 3 years ago
parent daffb79180
commit ada396b01f

@ -1,30 +1,52 @@
'use client' 'use client'
import React, { FC } from 'react' import React, { FC } from 'react'
import cn from 'classnames' import cn from 'classnames'
import { useTranslation } from 'react-i18next'
import Popover from '@/app/components/base/popover' import Popover from '@/app/components/base/popover'
import { TrashIcon } from '@heroicons/react/24/outline' import { TrashIcon } from '@heroicons/react/24/outline'
import s from './style.module.css' import s from './style.module.css'
const PinIcon = (
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M8.00012 9.99967L8.00012 14.6663M5.33346 4.87176V6.29217C5.33346 6.43085 5.33346 6.50019 5.31985 6.56652C5.30777 6.62536 5.2878 6.6823 5.26047 6.73579C5.22966 6.79608 5.18635 6.85023 5.09972 6.95852L4.0532 8.26667C3.60937 8.82145 3.38746 9.09884 3.38721 9.33229C3.38699 9.53532 3.4793 9.72738 3.63797 9.85404C3.82042 9.99967 4.17566 9.99967 4.88612 9.99967H11.1141C11.8246 9.99967 12.1798 9.99967 12.3623 9.85404C12.5209 9.72738 12.6133 9.53532 12.613 9.33229C12.6128 9.09884 12.3909 8.82145 11.947 8.26667L10.9005 6.95852C10.8139 6.85023 10.7706 6.79608 10.7398 6.73579C10.7125 6.6823 10.6925 6.62536 10.6804 6.56652C10.6668 6.50019 10.6668 6.43085 10.6668 6.29217V4.87176C10.6668 4.79501 10.6668 4.75664 10.6711 4.71879C10.675 4.68517 10.6814 4.6519 10.6903 4.61925C10.7003 4.5825 10.7146 4.54687 10.7431 4.47561L11.415 2.79582C11.611 2.30577 11.709 2.06074 11.6682 1.86404C11.6324 1.69203 11.5302 1.54108 11.3838 1.44401C11.2163 1.33301 10.9524 1.33301 10.4246 1.33301H5.57563C5.04782 1.33301 4.78391 1.33301 4.61646 1.44401C4.47003 1.54108 4.36783 1.69203 4.33209 1.86404C4.29122 2.06074 4.38923 2.30577 4.58525 2.79583L5.25717 4.47561C5.28567 4.54687 5.29992 4.5825 5.30995 4.61925C5.31886 4.6519 5.32526 4.68517 5.32912 4.71879C5.33346 4.75664 5.33346 4.79501 5.33346 4.87176Z" stroke="#667085" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"/>
</svg>
)
export interface IItemOperationProps { export interface IItemOperationProps {
className?: string className?: string
isPinned: boolean
isShowDelete: boolean
togglePin: () => void
onDelete: () => void onDelete: () => void
} }
const ItemOperation: FC<IItemOperationProps> = ({ const ItemOperation: FC<IItemOperationProps> = ({
className, className,
isPinned,
isShowDelete,
togglePin,
onDelete onDelete
}) => { }) => {
const { t } = useTranslation()
return ( return (
<Popover <Popover
htmlContent={ htmlContent={
<div className='w-full py-1' onClick={(e) => { <div className='w-full py-1' onClick={(e) => {
e.stopPropagation() e.stopPropagation()
}}> }}>
<div className={cn(s.actionItem, s.deleteActionItem, 'hover:bg-gray-50 group')} onClick={onDelete} > <div className={cn(s.actionItem, 'hover:bg-gray-50 group')} onClick={togglePin}>
{PinIcon}
<span className={s.actionName}>{isPinned ? t('explore.sideBar.action.unpin') : t('explore.sideBar.action.pin')}</span>
</div>
{isShowDelete && (
<div className={cn(s.actionItem, s.deleteActionItem, 'hover:bg-gray-50 group')} onClick={onDelete} >
<TrashIcon className={'w-4 h-4 stroke-current text-gray-500 stroke-2 group-hover:text-red-500'} /> <TrashIcon className={'w-4 h-4 stroke-current text-gray-500 stroke-2 group-hover:text-red-500'} />
<span className={cn(s.actionName, 'group-hover:text-red-500')}>{'Delete'}</span> <span className={cn(s.actionName, 'group-hover:text-red-500')}>{t('explore.sideBar.action.delete')}</span>
</div> </div>
)}
</div> </div>
} }
trigger='click' trigger='click'

@ -5,17 +5,25 @@ import ItemOperation from '@/app/components/explore/item-operation'
import s from './style.module.css' import s from './style.module.css'
export default function NavLink({ export interface IAppNavItemProps {
name,
id,
isSelected,
onDelete
}: {
name: string name: string
id: string id: string
isSelected: boolean isSelected: boolean
isPinned: boolean
togglePin: () => void
uninstallable: boolean
onDelete: (id: string) => void onDelete: (id: string) => void
}) { }
export default function AppNavItem({
name,
id,
isSelected,
isPinned,
togglePin,
uninstallable,
onDelete
}: IAppNavItemProps) {
const router = useRouter() const router = useRouter()
const url = `/explore/installed/${id}` const url = `/explore/installed/${id}`
@ -46,7 +54,9 @@ export default function NavLink({
!isSelected && ( !isSelected && (
<div className={cn(s.opBtn, 'shrink-0')} onClick={e => e.stopPropagation()}> <div className={cn(s.opBtn, 'shrink-0')} onClick={e => e.stopPropagation()}>
<ItemOperation <ItemOperation
// isShowDelete={} isPinned={isPinned}
togglePin={togglePin}
isShowDelete={!uninstallable}
onDelete={() => onDelete(id)} onDelete={() => onDelete(id)}
/> />
</div> </div>

@ -7,7 +7,8 @@ import cn from 'classnames'
import { useSelectedLayoutSegments } from 'next/navigation' import { useSelectedLayoutSegments } from 'next/navigation'
import Link from 'next/link' import Link from 'next/link'
import Item from './app-nav-item' import Item from './app-nav-item'
import { fetchInstalledAppList as doFetchInstalledAppList, uninstallApp } from '@/service/explore' import { fetchInstalledAppList as doFetchInstalledAppList, uninstallApp, updatePinStatus } from '@/service/explore'
import Toast from '../../base/toast'
const SelectedDiscoveryIcon = () => ( const SelectedDiscoveryIcon = () => (
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"> <svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
@ -39,6 +40,19 @@ const SideBar: FC<{
const handleDelete = async (id: string) => { const handleDelete = async (id: string) => {
await uninstallApp(id) await uninstallApp(id)
Toast.notify({
type: 'success',
message: t('common.api.Removed')
})
fetchInstalledAppList()
}
const handleUpdatePinStatus = async (id: string, isPinned: boolean) => {
await updatePinStatus(id, isPinned)
Toast.notify({
type: 'success',
message: t('common.api.success')
})
fetchInstalledAppList() fetchInstalledAppList()
} }
@ -66,13 +80,16 @@ const SideBar: FC<{
<div className='mt-10'> <div className='mt-10'>
<div className='pl-2 text-xs text-gray-500 font-medium uppercase'>{t('explore.sidebar.workspace')}</div> <div className='pl-2 text-xs text-gray-500 font-medium uppercase'>{t('explore.sidebar.workspace')}</div>
<div className='mt-3 space-y-1'> <div className='mt-3 space-y-1'>
{installedApps.map(({id, app : { name }}) => { {installedApps.map(({id, is_pinned, uninstallable, app : { name }}) => {
return ( return (
<Item <Item
key={id} key={id}
name={name} name={name}
id={id} id={id}
isSelected={lastSegment?.toLowerCase() === id} isSelected={lastSegment?.toLowerCase() === id}
isPinned={is_pinned}
togglePin={() => handleUpdatePinStatus(id, !is_pinned)}
uninstallable={uninstallable}
onDelete={handleDelete} onDelete={handleDelete}
/> />
) )

@ -16,6 +16,13 @@ const translation = {
title: 'Create app from {{name}}', title: 'Create app from {{name}}',
subTitle: 'App icon & name', subTitle: 'App icon & name',
nameRequired: 'App name is required', nameRequired: 'App name is required',
},
sideBar: {
action: {
pin: 'Pin',
unpin: 'Unpin',
delete: 'Delete',
}
} }
} }

@ -16,6 +16,13 @@ const translation = {
title: '从 {{name}} 创建应用程序', title: '从 {{name}} 创建应用程序',
subTitle: '应用程序图标和名称', subTitle: '应用程序图标和名称',
nameRequired: '应用程序名称不能为空', nameRequired: '应用程序名称不能为空',
},
sideBar: {
action: {
pin: '置顶',
unpin: '取消置顶',
delete: '删除',
}
} }
} }

@ -26,4 +26,5 @@ export type InstalledApp = {
app: AppBasicInfo; app: AppBasicInfo;
id: string; id: string;
uninstallable: boolean uninstallable: boolean
is_pinned: boolean
} }

@ -1,4 +1,4 @@
import { get, post, del } from './base' import { get, post, del, patch } from './base'
export const fetchAppList = () => { export const fetchAppList = () => {
return get('/explore/apps') return get('/explore/apps')
@ -23,3 +23,11 @@ export const installApp = (id: string) => {
export const uninstallApp = (id: string) => { export const uninstallApp = (id: string) => {
return del(`/installed-apps/${id}`) return del(`/installed-apps/${id}`)
} }
export const updatePinStatus = (id: string, isPinned: boolean) => {
return patch(`/installed-apps/${id}`, {
body: {
is_pinned: isPinned
}
})
}

Loading…
Cancel
Save