import { useTranslation } from 'react-i18next' import { FormEventHandler, useEffect, useMemo, useState } from 'react' import { getJSON } from '@/infrastructure/fetch-json' import useAsync from '@/shared/hooks/use-async' import { debugConsole } from '@/utils/debugging' import * as eventTracking from '@/infrastructure/event-tracking' import { useFileTreeActionable } from '@/features/file-tree/contexts/file-tree-actionable' import { useFileTreeCreateForm } from '@/features/file-tree/contexts/file-tree-create-form' import { useFileTreeCreateName } from '@/features/file-tree/contexts/file-tree-create-name' import FileTreeCreateNameInput from '@/features/file-tree/components/file-tree-create/file-tree-create-name-input' import ErrorMessage from '@/features/file-tree/components/file-tree-create/error-message' import OLFormGroup from '@/shared/components/ol/ol-form-group' import OLFormLabel from '@/shared/components/ol/ol-form-label' import OLFormSelect from '@/shared/components/ol/ol-form-select' import Notification from '@/shared/components/notification' export type ZoteroGroup = { id: string name: string } type ZoteroCollection = { key: string name: string parentCollection: string | null } function collectionOptions(collections: ZoteroCollection[]) { const byKey = new Map(collections.map(c => [c.key, c])) return collections.map(collection => { const path = [collection.name] const visited = new Set([collection.key]) let parentKey = collection.parentCollection while (parentKey && !visited.has(parentKey)) { const parent = byKey.get(parentKey) if (!parent) break path.unshift(parent.name) visited.add(parentKey) parentKey = parent.parentCollection } return { key: collection.key, label: path.join(' / ') } }).sort((a, b) => a.label.localeCompare(b.label)) } type FileTreeImportFromZoteroProps = { groups: ZoteroGroup[] } export default function FileTreeImportFromZotero({ groups }: FileTreeImportFromZoteroProps) { const { t } = useTranslation() const { name } = useFileTreeCreateName() const { setValid } = useFileTreeCreateForm() const { finishCreatingLinkedFile, error, inFlight } = useFileTreeActionable() const [selectedGroupId, setSelectedGroupId] = useState('') const [selectedCollectionKey, setSelectedCollectionKey] = useState('') const { runAsync, reset, data: collections, isLoading: collectionsLoading, isError: collectionsError, } = useAsync({ status: 'pending' }) const [selectedFormat, setSelectedFormat] = useState('bibtex') const sortedCollections = useMemo(() => collectionOptions(collections || []), [collections]) useEffect(() => { const controller = new AbortController() reset() const query = selectedGroupId ? `?group_id=${encodeURIComponent(selectedGroupId)}` : '' runAsync(getJSON(`/user/zotero/collections${query}`, { signal: controller.signal, })) .catch(err => { debugConsole.error(err?.data?.message || err?.message || err) }) return () => controller.abort() }, [selectedGroupId, runAsync, reset]) useEffect(() => { setValid(!!name) }, [name, setValid]) const handleSubmit: FormEventHandler = event => { event.preventDefault() eventTracking.sendMB('new-file-created', { method: 'zotero', extension: name.split('.').length > 1 ? name.split('.').pop() : '', }) finishCreatingLinkedFile({ name, provider: 'zotero', data: { group_id: selectedGroupId || undefined, collection_key: selectedCollectionKey || undefined, format: selectedFormat, } }) } return ( <>

{t('import_a_bibtex_file_from_your_provider_account', { provider: 'Zotero' })}

{t('library')} ) => { setSelectedGroupId(e.target.value) setSelectedCollectionKey('') }} > {groups?.map(g => ( ))} {t('collection')} ) => setSelectedCollectionKey(e.target.value) } > {!selectedGroupId && ( )} {sortedCollections.map(collection => ( ))} {collectionsError && ( )} {t('format')} ) => setSelectedFormat(e.target.value) } > {inFlight && (
{t('importing') + '…'}
)} {error && } ) }