405c1d27c9
Add a 'Python packages' button to the file-tree toolbar that opens a modal to edit the project's requirements.vrf (one package per line, pip syntax), backed by GET/POST /project/:id/python-requirements (read via ProjectEntityHandler, write via EditorController.upsertDocWithPath, write-gated). The .vrf file is now hidden from the file tree, so it is managed only through this editor rather than appearing as a loose file. Adds python_packages / python_packages_help i18n. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
95 lines
3.0 KiB
TypeScript
95 lines
3.0 KiB
TypeScript
import { useCallback, useEffect, useState } from 'react'
|
|
import { useTranslation } from 'react-i18next'
|
|
import {
|
|
OLModal,
|
|
OLModalBody,
|
|
OLModalFooter,
|
|
OLModalHeader,
|
|
OLModalTitle,
|
|
} from '@/shared/components/ol/ol-modal'
|
|
import OLButton from '@/shared/components/ol/ol-button'
|
|
import OLNotification from '@/shared/components/ol/ol-notification'
|
|
import LoadingSpinner from '@/shared/components/loading-spinner'
|
|
import { useProjectContext } from '@/shared/context/project-context'
|
|
import { getJSON, postJSON } from '@/infrastructure/fetch-json'
|
|
|
|
// Editor for the project's Python dependencies (requirements.vrf), reached from
|
|
// the file-tree toolbar. The file itself is hidden from the tree; this modal is
|
|
// the only entry point. One package per line, pip syntax (e.g. `openpyxl==3.1.5`).
|
|
export default function PythonRequirementsModal({
|
|
show,
|
|
onHide,
|
|
}: {
|
|
show: boolean
|
|
onHide: () => void
|
|
}) {
|
|
const { t } = useTranslation()
|
|
const { projectId } = useProjectContext()
|
|
const [content, setContent] = useState('')
|
|
const [loading, setLoading] = useState(false)
|
|
const [saving, setSaving] = useState(false)
|
|
const [error, setError] = useState<string | null>(null)
|
|
|
|
useEffect(() => {
|
|
if (!show) return
|
|
setError(null)
|
|
setLoading(true)
|
|
getJSON<{ content: string }>(`/project/${projectId}/python-requirements`)
|
|
.then(data => setContent(data.content || ''))
|
|
.catch(() => setError(t('generic_something_went_wrong')))
|
|
.finally(() => setLoading(false))
|
|
}, [show, projectId, t])
|
|
|
|
const handleSave = useCallback(() => {
|
|
setSaving(true)
|
|
setError(null)
|
|
postJSON(`/project/${projectId}/python-requirements`, {
|
|
body: { content },
|
|
})
|
|
.then(() => onHide())
|
|
.catch(() => setError(t('generic_something_went_wrong')))
|
|
.finally(() => setSaving(false))
|
|
}, [projectId, content, onHide, t])
|
|
|
|
return (
|
|
<OLModal show={show} onHide={onHide}>
|
|
<OLModalHeader closeButton>
|
|
<OLModalTitle>{t('python_packages')}</OLModalTitle>
|
|
</OLModalHeader>
|
|
<OLModalBody>
|
|
<p className="text-muted">{t('python_packages_help')}</p>
|
|
{error && (
|
|
<OLNotification type="error" content={error} className="mb-3" />
|
|
)}
|
|
{loading ? (
|
|
<LoadingSpinner />
|
|
) : (
|
|
<textarea
|
|
className="form-control"
|
|
rows={10}
|
|
spellCheck={false}
|
|
style={{ fontFamily: 'monospace' }}
|
|
value={content}
|
|
onChange={e => setContent(e.target.value)}
|
|
placeholder={'openpyxl==3.1.5\nrequests'}
|
|
aria-label={t('python_packages')}
|
|
/>
|
|
)}
|
|
</OLModalBody>
|
|
<OLModalFooter>
|
|
<OLButton variant="secondary" onClick={onHide} disabled={saving}>
|
|
{t('cancel')}
|
|
</OLButton>
|
|
<OLButton
|
|
variant="primary"
|
|
onClick={handleSave}
|
|
disabled={loading || saving}
|
|
isLoading={saving}
|
|
>
|
|
{t('save')}
|
|
</OLButton>
|
|
</OLModalFooter>
|
|
</OLModal>
|
|
)
|
|
}
|