import { LoaderCircleIcon, PuzzleIcon } from "lucide-react" import { useNavigate, useSearch } from "@tanstack/react-router" import { useTranslation } from "react-i18next" import { toast } from "sonner" import { Button } from "@/components/ui/button" import { ScrollArea } from "@/components/ui/scroll-area" import { providerStoreActions, useAppStore, useProviderStore } from "@/store" import { ProviderSidebarSearch } from "./provider-sidebar-search" import { ProviderSidebarUploaderSection } from "./provider-sidebar-uploader-section" import { emptyProviderConfigMap, resolveErrorMessage, resolveProviderSelectionState, resolveSelectableConfigId, } from "./utils" import type { ProviderDraftConfigItem, ProviderUploaderConfigItem, ProviderUploaderSummary, } from "./types" interface ProviderSidebarProps { draftConfigMap: Record onCreateIntent: (uploaderId: string) => void onRenameIntent: (uploaderId: string, configId: string, configName: string) => void onCopyIntent: (uploaderId: string, configId: string, configName: string) => void onDeleteIntent: (uploaderId: string, configId: string) => void } interface FilteredUploaderItem { uploader: ProviderUploaderSummary persistedConfigs: ProviderUploaderConfigItem[] visibleConfigs: Array defaultConfigId: string | undefined isLoadingConfigs: boolean canCreateConfig: boolean } export function ProviderSidebar({ draftConfigMap, onCreateIntent, onRenameIntent, onCopyIntent, onDeleteIntent, }: ProviderSidebarProps) { const { t } = useTranslation() const navigate = useNavigate() const search = useSearch({ from: "/main/providers" }) const appConfig = useAppStore.use.appConfig() const providers = useAppStore.use.providers() const providerSchemas = useAppStore.use.providerSchemas() const loadingMap = useProviderStore.use.isLoadingByProvider() const isLoadingUploaders = useProviderStore.use.isHydrating() const searchValue = useProviderStore.use.searchValue() const expandedUploaderIds = useProviderStore.use.expandedProviderIds() const queriedUploaderId = search.uploader ?? null const queriedConfigId = search.configId ?? null const configMap = appConfig?.uploader ?? emptyProviderConfigMap const uploaders = providers.filter((provider) => provider.visible !== false) const { selectedUploaderId, selectedConfigId } = resolveProviderSelectionState({ queriedUploaderId, queriedConfigId, appConfigUploaderId: appConfig?.picBed.uploader, visibleProviders: uploaders, configMap, draftConfigMap, }) const normalizedSearch = searchValue.trim().toLowerCase() const hasSearch = normalizedSearch.length > 0 const navigateToSelection = ( uploaderId: string | undefined, configId: string | undefined ) => { navigate({ to: "/main/providers", search: (prev) => ({ ...prev, uploader: uploaderId, configId, }), }) } const handleSelectUploader = (uploaderId: string) => { const nextConfigId = resolveSelectableConfigId( configMap[uploaderId], draftConfigMap[uploaderId], null ) providerStoreActions.ensureExpanded(uploaderId) navigateToSelection(uploaderId, nextConfigId ?? undefined) } const handleSelectConfig = (uploaderId: string, configId: string) => { navigateToSelection(uploaderId, configId) } const handleSetDefaultUploader = async (uploaderId: string) => { try { await providerStoreActions.setDefaultProvider(uploaderId) toast.success(t("SUCCESS")) } catch (error) { toast.error(resolveErrorMessage(error, t("FAILED"))) } } const filteredUploaders: FilteredUploaderItem[] = uploaders.flatMap((uploader) => { const configState = configMap[uploader.id] const persistedConfigs = configState?.configList ?? [] const draftConfig = draftConfigMap[uploader.id] const allConfigs = draftConfig ? [...persistedConfigs, draftConfig] : persistedConfigs const schema = providerSchemas[uploader.id] const isEmptySchema = schema !== undefined && schema.config.length === 0 const canCreateConfig = !(isEmptySchema && persistedConfigs.length >= 1) if (!hasSearch) { return [ { uploader, persistedConfigs, visibleConfigs: allConfigs, defaultConfigId: configState?.defaultId, isLoadingConfigs: Boolean(loadingMap[uploader.id]), canCreateConfig, }, ] } const matchedUploaderName = uploader.name.toLowerCase().includes(normalizedSearch) if (matchedUploaderName) { return [ { uploader, persistedConfigs, visibleConfigs: allConfigs, defaultConfigId: configState?.defaultId, isLoadingConfigs: Boolean(loadingMap[uploader.id]), canCreateConfig, }, ] } const matchedConfigs = allConfigs.filter((config) => config._configName.toLowerCase().includes(normalizedSearch) ) if (matchedConfigs.length === 0) { return [] } return [ { uploader, persistedConfigs, visibleConfigs: matchedConfigs, defaultConfigId: configState?.defaultId, isLoadingConfigs: Boolean(loadingMap[uploader.id]), canCreateConfig, }, ] }) return ( ) }