'use client' import type { CreateAppModalProps } from '@/app/components/explore/create-app-modal' import type { App } from '@/models/explore' import { useDebounceFn } from 'ahooks' import { useQueryState } from 'nuqs' import * as React from 'react' import { useCallback, useMemo, useState } from 'react' import { useTranslation } from 'react-i18next' import { useContext, useContextSelector } from 'use-context-selector' import DSLConfirmModal from '@/app/components/app/create-from-dsl-modal/dsl-confirm-modal' import Button from '@/app/components/base/button' import Input from '@/app/components/base/input' import Loading from '@/app/components/base/loading' import AppCard from '@/app/components/explore/app-card' import Banner from '@/app/components/explore/banner/banner' import Category from '@/app/components/explore/category' import CreateAppModal from '@/app/components/explore/create-app-modal' import ExploreContext from '@/context/explore-context' import { useGlobalPublicStore } from '@/context/global-public-context' import { useImportDSL } from '@/hooks/use-import-dsl' import { DSLImportMode, } from '@/models/app' import { fetchAppDetail } from '@/service/explore' import { useExploreAppList } from '@/service/use-explore' import { cn } from '@/utils/classnames' import TryApp from '../try-app' import s from './style.module.css' // Extend: start Explore Add Search import SearchInput from '@/app/components/base/search-input' import TagFilter from '@/app/components/base/tag-management/filter' // Extend: stop Explore Add Search type AppsProps = { onSuccess?: () => void } const Apps = ({ onSuccess, }: AppsProps) => { const { t } = useTranslation() const { systemFeatures } = useGlobalPublicStore() const { hasEditPermission } = useContext(ExploreContext) const allCategoriesEn = t('apps.allCategories', { ns: 'explore', lng: 'en' }) // Extend: start Explore Add Search const [tagFilterValue, setTagFilterValue] = useState([]) const [keywordsValue, setKeywordsValue] = useState('') // Extend: stop Explore Add Search const [keywords, setKeywords] = useState('') const [searchKeywords, setSearchKeywords] = useState('') const hasFilterCondition = !!keywords const handleResetFilter = useCallback(() => { setKeywords('') setSearchKeywords('') }, []) const { run: handleSearch } = useDebounceFn(() => { setSearchKeywords(keywords) }, { wait: 500 }) const handleKeywordsChange = (value: string) => { setKeywords(value) handleSearch() } const [currCategory, setCurrCategory] = useQueryState('category', { defaultValue: allCategoriesEn, }) const { data, isLoading, isError, // extend: start sync app refetch, // extend: stop sync app } = useExploreAppList() // extend: start sync app // Get recommended apps list to check if app is synced const recommendedAppIds = useMemo(() => { if (!data) return new Set() // Extract app_id from allList to check sync status return new Set(data.allList.map(item => item.app_id)) }, [data]) // extend: stop sync app // Extend: start Filtered list with search and tag filter const filteredListExtend = useMemo(() => { if (!data) return [] let result = data.allList // Apply category filter if (currCategory !== allCategoriesEn) { result = result.filter(item => item.category === currCategory) } // Apply tag filter if (tagFilterValue.length > 0) { result = result.filter(item => tagFilterValue.includes(item.category)) } // Apply keyword search if (keywordsValue.length > 0) { const lowerCaseKeywords = keywordsValue.toLowerCase() result = result.filter(item => item.description?.toLowerCase().includes(lowerCaseKeywords) || item.app?.name?.toLowerCase().includes(lowerCaseKeywords) ) } return result }, [data, currCategory, allCategoriesEn, tagFilterValue, keywordsValue]) const handleTagsChange = (value: string[]) => { setTagFilterValue(value) } // Extend: stop Filtered list with search and tag filter const [currApp, setCurrApp] = React.useState(null) const [isShowCreateModal, setIsShowCreateModal] = React.useState(false) const { handleImportDSL, handleImportDSLConfirm, versions, isFetching, } = useImportDSL() const [showDSLConfirmModal, setShowDSLConfirmModal] = useState(false) const isShowTryAppPanel = useContextSelector(ExploreContext, ctx => ctx.isShowTryAppPanel) const setShowTryAppPanel = useContextSelector(ExploreContext, ctx => ctx.setShowTryAppPanel) const hideTryAppPanel = useCallback(() => { setShowTryAppPanel(false) }, [setShowTryAppPanel]) const appParams = useContextSelector(ExploreContext, ctx => ctx.currentApp) const handleShowFromTryApp = useCallback(() => { setCurrApp(appParams?.app || null) setIsShowCreateModal(true) }, [appParams?.app]) const onCreate: CreateAppModalProps['onConfirm'] = async ({ name, icon_type, icon, icon_background, description, }) => { hideTryAppPanel() const { export_data } = await fetchAppDetail( currApp?.app.id as string, ) const payload = { mode: DSLImportMode.YAML_CONTENT, yaml_content: export_data, name, icon_type, icon, icon_background, description, } await handleImportDSL(payload, { onSuccess: () => { setIsShowCreateModal(false) }, onPending: () => { setShowDSLConfirmModal(true) }, }) } const onConfirmDSL = useCallback(async () => { await handleImportDSLConfirm({ onSuccess, }) }, [handleImportDSLConfirm, onSuccess]) if (isLoading) { return (
) } if (isError || !data) return null const { categories } = data return (
{systemFeatures.enable_explore_banner && (
)}
{/* Extend: start Explore Add Search */}
{/* Extend: stop Explore Add Search */}
{isShowCreateModal && ( setIsShowCreateModal(false)} /> )} { showDSLConfirmModal && ( setShowDSLConfirmModal(false)} onConfirm={onConfirmDSL} confirmDisabled={isFetching} /> ) } {isShowTryAppPanel && ( )}
) } export default React.memo(Apps)