/
vshmidt
/
label-studio
Обзор
Документация
Войти
/
vshmidt
/
label-studio
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
Аналитика
Безопасность
develop
web/apps/labelstudio/src/pages/CreateProject/CreateProject.jsx
257 строк
8 KB
Raul Martin
feat: Migrate SCSS to plain CSS with PostCSS, replace Stylelint with Biome (#9584)
12 мар 2026, 20:57
Не верифицирован
12 мар 2026, 20:57
3f31128
Код
Авторство
О чём код?
import { EnterpriseBadge, Select, Typography } from "@humansignal/ui"; import React from "react"; import { useHistory } from "react-router"; import { ToggleItems } from "../../components"; import { Button } from "@humansignal/ui"; import { Modal } from "../../components/Modal/Modal"; import { Space } from "../../components/Space/Space"; import { HeidiTips } from "../../components/HeidiTips/HeidiTips"; import { useAPI } from "../../providers/ApiProvider"; import { cn } from "../../utils/bem"; import { ConfigPage } from "./Config/Config"; import "./CreateProject.prefix.css"; import { ImportPage } from "./Import/Import"; import { useImportPage } from "./Import/useImportPage"; import { useDraftProject } from "./utils/useDraftProject"; import { Input, TextArea } from "../../components/Form"; import { FF_LSDV_E_297, isFF } from "../../utils/feature-flags"; import { createURL } from "../../components/HeidiTips/utils"; const ProjectName = ({ name, setName, onSaveName, onSubmit, error, description, setDescription, show = true }) => !show ? null : ( <form className={cn("project-name").toClassName()} onSubmit={(e) => { e.preventDefault(); onSubmit(); }} > <div className="w-full flex flex-col gap-2"> <label className="w-full" htmlFor="project_name"> Project Name </label> <Input name="name" id="project_name" value={name} onChange={(e) => setName(e.target.value)} onBlur={onSaveName} className="project-title w-full" /> {error && <span className="-mt-1 text-negative-content">{error}</span>} </div> <div className="w-full flex flex-col gap-2"> <label className="w-full" htmlFor="project_description"> Description </label> <TextArea name="description" id="project_description" placeholder="Optional description of your project" rows="4" style={{ minHeight: 100 }} value={description} onChange={(e) => setDescription(e.target.value)} className="project-description w-full" /> </div> {isFF(FF_LSDV_E_297) && ( <div className="w-full flex flex-col gap-2"> <label> Workspace <EnterpriseBadge className="ml-tight" /> </label> <Select placeholder="Select an option" disabled options={[]} triggerClassName="!flex-1" /> <Typography size="small" className="mt-tight mb-wider"> Simplify project management by organizing projects into workspaces.{" "} <a href={createURL( "https://docs.humansignal.com/guide/manage_projects#Create-workspaces-to-organize-projects", { experiment: "project_creation_dropdown", treatment: "simplify_project_management", }, )} target="_blank" rel="noreferrer" className="underline hover:no-underline" > Learn more </a> </Typography> <HeidiTips collection="projectCreation" /> </div> )} </form> ); export const CreateProject = ({ onClose }) => { const [step, _setStep] = React.useState("name"); // name | import | config const [waiting, setWaitingStatus] = React.useState(false); const { project, setProject: updateProject } = useDraftProject(); const history = useHistory(); const api = useAPI(); const [name, setName] = React.useState(""); const [error, setError] = React.useState(); const [description, setDescription] = React.useState(""); const [sample, setSample] = React.useState(null); const setStep = React.useCallback((step) => { _setStep(step); const eventNameMap = { name: "project_name", import: "data_import", config: "labeling_setup", }; __lsa(`create_project.tab.${eventNameMap[step]}`); }, []); React.useEffect(() => { setError(null); }, [name]); const { columns, uploading, uploadDisabled, finishUpload, pageProps, uploadSample } = useImportPage(project, sample); const rootClass = cn("create-project"); const tabClass = rootClass.elem("tab"); const steps = { name: <span className={tabClass.mod({ disabled: !!error }).toClassName()}>Project Name</span>, import: <span className={tabClass.mod({ disabled: uploadDisabled }).toClassName()}>Data Import</span>, config: "Labeling Setup", }; // name intentionally skipped from deps: // this should trigger only once when we got project loaded React.useEffect(() => { project && !name && setName(project.title); }, [project]); const projectBody = React.useMemo( () => ({ title: name, description, label_config: project?.label_config ?? "<View></View>", }), [name, description, project?.label_config], ); const onCreate = React.useCallback(async () => { // First, persist project with label_config so import/reimport validates against it const response = await api.callApi("updateProject", { params: { pk: project.id, }, body: { ...projectBody, is_draft: false }, }); if (response === null) return; const imported = await finishUpload(); if (!imported) return; setWaitingStatus(true); if (sample) await uploadSample(sample); __lsa("create_project.create", { sample: sample?.url }); setWaitingStatus(false); history.push(`/projects/${response.id}/data`); }, [project, projectBody, finishUpload]); const onSaveName = async () => { if (error) return; const res = await api.callApi("updateProjectRaw", { params: { pk: project.id, }, body: { title: name, }, }); if (res.ok) return; const err = await res.json(); setError(err.validation_errors?.title); }; const onDelete = React.useCallback(() => { const performClose = async () => { setWaitingStatus(true); if (project) await api.callApi("deleteProject", { params: { pk: project.id, }, }); setWaitingStatus(false); updateProject(null); onClose?.(); }; performClose(); }, [project]); return ( <Modal onHide={onDelete} closeOnClickOutside={false} allowToInterceptEscape fullscreen visible bare> <div className={rootClass}> <Modal.Header> <h1>Create Project</h1> <ToggleItems items={steps} active={step} onSelect={setStep} /> <Space> <Button variant="negative" look="outlined" onClick={onDelete} waiting={waiting} aria-label="Cancel project creation" > Cancel </Button> <Button look="primary" onClick={onCreate} waiting={waiting || uploading} waitingClickable={false} disabled={!project || uploadDisabled || error} > Save </Button> </Space> </Modal.Header> <ProjectName name={name} setName={setName} error={error} onSaveName={onSaveName} onSubmit={onCreate} description={description} setDescription={setDescription} show={step === "name"} /> <ImportPage project={project} show={step === "import"} sample={sample} onSampleDatasetSelect={setSample} openLabelingConfig={() => setStep("config")} {...pageProps} /> <ConfigPage project={project} onUpdate={(config) => { updateProject({ ...project, label_config: config }); }} show={step === "config"} columns={columns} disableSaveButton={true} /> </div> </Modal> ); };