Move dataset upload into modal wall view
This commit is contained in:
@@ -14,6 +14,7 @@ import {
|
|||||||
Gauge,
|
Gauge,
|
||||||
HardDrive,
|
HardDrive,
|
||||||
Layers3,
|
Layers3,
|
||||||
|
Plus,
|
||||||
Play,
|
Play,
|
||||||
RefreshCcw,
|
RefreshCcw,
|
||||||
ShieldCheck,
|
ShieldCheck,
|
||||||
@@ -22,6 +23,7 @@ import {
|
|||||||
Trash2,
|
Trash2,
|
||||||
UploadCloud,
|
UploadCloud,
|
||||||
PackageOpen,
|
PackageOpen,
|
||||||
|
X,
|
||||||
Wand2,
|
Wand2,
|
||||||
Zap
|
Zap
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
@@ -616,6 +618,7 @@ function App() {
|
|||||||
const [log, setLog] = useState("");
|
const [log, setLog] = useState("");
|
||||||
const [busy, setBusy] = useState(false);
|
const [busy, setBusy] = useState(false);
|
||||||
const [actionError, setActionError] = useState("");
|
const [actionError, setActionError] = useState("");
|
||||||
|
const [uploadModalOpen, setUploadModalOpen] = useState(false);
|
||||||
const [datasetName, setDatasetName] = useState("demo_dataset");
|
const [datasetName, setDatasetName] = useState("demo_dataset");
|
||||||
const [datasetDescription, setDatasetDescription] = useState("");
|
const [datasetDescription, setDatasetDescription] = useState("");
|
||||||
const [copyDatasetName, setCopyDatasetName] = useState("");
|
const [copyDatasetName, setCopyDatasetName] = useState("");
|
||||||
@@ -964,6 +967,7 @@ function App() {
|
|||||||
body: JSON.stringify({ name: datasetName, description: datasetDescription })
|
body: JSON.stringify({ name: datasetName, description: datasetDescription })
|
||||||
});
|
});
|
||||||
setSelectedDatasetName(datasetName);
|
setSelectedDatasetName(datasetName);
|
||||||
|
setUploadModalOpen(false);
|
||||||
await refresh();
|
await refresh();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setActionError(String(err));
|
setActionError(String(err));
|
||||||
@@ -989,6 +993,9 @@ function App() {
|
|||||||
throw new Error(`上传失败,无法连接后端 ${API_BASE}:${err instanceof Error ? err.message : String(err)}`);
|
throw new Error(`上传失败,无法连接后端 ${API_BASE}:${err instanceof Error ? err.message : String(err)}`);
|
||||||
}
|
}
|
||||||
if (!res.ok) throw new Error(await res.text());
|
if (!res.ok) throw new Error(await res.text());
|
||||||
|
setSelectedDatasetName(datasetName);
|
||||||
|
setUploadModalOpen(false);
|
||||||
|
setUploadFiles(null);
|
||||||
await refresh();
|
await refresh();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setActionError(String(err));
|
setActionError(String(err));
|
||||||
@@ -1394,37 +1401,11 @@ function App() {
|
|||||||
<div className="panelHead">
|
<div className="panelHead">
|
||||||
<div>
|
<div>
|
||||||
<p className="eyebrow">Dataset Bench</p>
|
<p className="eyebrow">Dataset Bench</p>
|
||||||
<h2>上传与管理</h2>
|
<h2>数据集管理</h2>
|
||||||
</div>
|
</div>
|
||||||
<Database size={22} />
|
<button className="iconButton addDatasetButton" type="button" onClick={() => setUploadModalOpen(true)} title="新建或上传数据集">
|
||||||
</div>
|
<Plus size={19} />
|
||||||
<div className="datasetForm">
|
|
||||||
<label className="field compact">
|
|
||||||
<span>数据集名称</span>
|
|
||||||
<input value={datasetName} onChange={(event) => setDatasetName(event.target.value)} />
|
|
||||||
</label>
|
|
||||||
<label className="field compact">
|
|
||||||
<span>说明</span>
|
|
||||||
<input value={datasetDescription} onChange={(event) => setDatasetDescription(event.target.value)} />
|
|
||||||
</label>
|
|
||||||
{datasetNameExists && <small className="formWarn">已存在同名数据集,只能上传追加,不能重复创建。</small>}
|
|
||||||
<div className="segmented">
|
|
||||||
{(["images", "masks"] as const).map((kind) => (
|
|
||||||
<button key={kind} className={uploadKind === kind ? "active" : ""} onClick={() => setUploadKind(kind)}>
|
|
||||||
{kind}
|
|
||||||
</button>
|
</button>
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
<label className="drop">
|
|
||||||
{uploadFiles?.length ? <PackageOpen size={24} /> : <UploadCloud size={24} />}
|
|
||||||
<span>{uploadFiles?.length ? `${uploadFiles.length} 个文件待上传` : `选择 ${uploadKind} 图片、视频或压缩包`}</span>
|
|
||||||
<small>.png/.jpg/.tif/.mp4/.zip/.tar.gz 均可上传</small>
|
|
||||||
<input multiple type="file" accept={uploadAccept} onChange={(event) => setUploadFiles(event.target.files)} />
|
|
||||||
</label>
|
|
||||||
<div className="buttonRow">
|
|
||||||
<button className="primary" disabled={busy || datasetNameExists} onClick={createDataset}><Boxes size={17} />创建</button>
|
|
||||||
<button className="primary secondary" disabled={busy || !uploadFiles?.length} onClick={uploadDatasetFiles}><UploadCloud size={17} />上传</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="datasetManager">
|
<div className="datasetManager">
|
||||||
@@ -1521,8 +1502,6 @@ function App() {
|
|||||||
{selectedDataset ? (
|
{selectedDataset ? (
|
||||||
<>
|
<>
|
||||||
<DatasetDetail dataset={selectedDataset} validation={selectedValidation} />
|
<DatasetDetail dataset={selectedDataset} validation={selectedValidation} />
|
||||||
{selectedValidation && <DatasetQuality validation={selectedValidation} />}
|
|
||||||
<DatasetYoloOutputs dataset={selectedDataset} outputs={selectedYoloOutputs} />
|
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<p className="muted">左侧创建或选择一个数据集后,这里会显示图片、masks、配对和异常项。</p>
|
<p className="muted">左侧创建或选择一个数据集后,这里会显示图片、masks、配对和异常项。</p>
|
||||||
@@ -1530,6 +1509,50 @@ function App() {
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
{uploadModalOpen && (
|
||||||
|
<div className="modalOverlay" data-page-section="datasets" role="dialog" aria-modal="true" aria-label="上传数据集">
|
||||||
|
<div className="datasetUploadModal">
|
||||||
|
<div className="modalHead">
|
||||||
|
<div>
|
||||||
|
<p className="eyebrow">Upload</p>
|
||||||
|
<h2>新建或追加数据集</h2>
|
||||||
|
</div>
|
||||||
|
<button className="iconButton" type="button" onClick={() => setUploadModalOpen(false)} title="关闭">
|
||||||
|
<X size={18} />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div className="datasetForm">
|
||||||
|
<label className="field compact">
|
||||||
|
<span>数据集名称</span>
|
||||||
|
<input value={datasetName} onChange={(event) => setDatasetName(event.target.value)} />
|
||||||
|
</label>
|
||||||
|
<label className="field compact">
|
||||||
|
<span>说明</span>
|
||||||
|
<input value={datasetDescription} onChange={(event) => setDatasetDescription(event.target.value)} />
|
||||||
|
</label>
|
||||||
|
{datasetNameExists && <small className="formWarn">已存在同名数据集,不能重复创建,但可以继续上传追加文件。</small>}
|
||||||
|
<div className="segmented">
|
||||||
|
{(["images", "masks"] as const).map((kind) => (
|
||||||
|
<button key={kind} type="button" className={uploadKind === kind ? "active" : ""} onClick={() => setUploadKind(kind)}>
|
||||||
|
{kind}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<label className="drop">
|
||||||
|
{uploadFiles?.length ? <PackageOpen size={24} /> : <UploadCloud size={24} />}
|
||||||
|
<span>{uploadFiles?.length ? `${uploadFiles.length} 个文件待上传` : `选择 ${uploadKind} 图片、视频或压缩包`}</span>
|
||||||
|
<small>.png/.jpg/.tif/.mp4/.zip/.tar.gz 均可上传</small>
|
||||||
|
<input multiple type="file" accept={uploadAccept} onChange={(event) => setUploadFiles(event.target.files)} />
|
||||||
|
</label>
|
||||||
|
<div className="modalActions">
|
||||||
|
<button className="primary" disabled={busy || datasetNameExists} onClick={createDataset}><Boxes size={17} />创建数据集</button>
|
||||||
|
<button className="primary secondary" disabled={busy || !uploadFiles?.length} onClick={uploadDatasetFiles}><UploadCloud size={17} />上传文件</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
<section className="grid two" id="inference" data-page-section="inference">
|
<section className="grid two" id="inference" data-page-section="inference">
|
||||||
<div className="panel">
|
<div className="panel">
|
||||||
<div className="panelHead">
|
<div className="panelHead">
|
||||||
@@ -2159,73 +2182,70 @@ function AcceptanceArtifactLinks({ artifacts }: { artifacts?: AcceptancePayload[
|
|||||||
|
|
||||||
function DatasetDetail({ dataset, validation }: { dataset: UploadedDataset; validation?: DatasetValidation }) {
|
function DatasetDetail({ dataset, validation }: { dataset: UploadedDataset; validation?: DatasetValidation }) {
|
||||||
const rows = datasetPairRows(dataset);
|
const rows = datasetPairRows(dataset);
|
||||||
const images = datasetFiles(dataset, "images");
|
|
||||||
const masks = datasetFiles(dataset, "masks");
|
|
||||||
const problemRows = rows.filter((row) => row.status !== "paired");
|
const problemRows = rows.filter((row) => row.status !== "paired");
|
||||||
const shapeErrors = validation?.checks.find((check) => check.name === "mask_shapes_match")?.errors ?? [];
|
const shapeErrors = validation?.checks.find((check) => check.name === "mask_shapes_match")?.errors ?? [];
|
||||||
const ready = validation?.ready.mask;
|
const ready = validation?.ready.mask;
|
||||||
return (
|
return (
|
||||||
<div className="datasetDetailStack">
|
<div className="datasetDetailStack">
|
||||||
<div className="datasetSummaryStrip">
|
<div className="datasetWallStats">
|
||||||
<div><span>images / video</span><strong>{dataset.counts.images}</strong></div>
|
<span>{dataset.counts.images} images / video</span>
|
||||||
<div><span>masks</span><strong>{dataset.counts.masks}</strong></div>
|
<span>{dataset.counts.masks} masks</span>
|
||||||
<div><span>paired</span><strong>{validation?.pairs.image_mask ?? rows.filter((row) => row.status === "paired").length}</strong></div>
|
<span>{validation?.pairs.image_mask ?? rows.filter((row) => row.status === "paired").length} paired</span>
|
||||||
<div><span>status</span><strong>{ready ? "MASK READY" : "CHECK"}</strong></div>
|
<strong>{ready ? "MASK READY" : "CHECK"}</strong>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="datasetPreviewBand">
|
<div className="imageWall">
|
||||||
{[...images.slice(0, 4), ...masks.slice(0, 4)].slice(0, 8).map((item) => (
|
|
||||||
<a key={item.relative_path} href={artifactUrl(item.relative_path)} target="_blank" rel="noreferrer">
|
|
||||||
{item.previewable ? <img src={artifactUrl(item.relative_path)} alt={item.name} /> : <FileImage size={22} />}
|
|
||||||
<span>{item.name}</span>
|
|
||||||
</a>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="fileTable">
|
|
||||||
<div className="fileTableHead">
|
|
||||||
<span>配对</span>
|
|
||||||
<span>images / video</span>
|
|
||||||
<span>大小</span>
|
|
||||||
<span>masks</span>
|
|
||||||
<span>大小</span>
|
|
||||||
<span>状态</span>
|
|
||||||
</div>
|
|
||||||
<div className="fileTableBody">
|
|
||||||
{rows.length ? rows.slice(0, 500).map((row) => (
|
{rows.length ? rows.slice(0, 500).map((row) => (
|
||||||
<div key={row.key} className={`fileTableRow ${row.status}`}>
|
<article key={row.key} className={`wallCard ${row.status}`}>
|
||||||
<span>{row.stem}</span>
|
<span className="wallBadge">{row.status === "paired" ? "已配对" : row.status === "missing_mask" ? "缺少 mask" : "多余 mask"}</span>
|
||||||
<a href={row.image ? artifactUrl(row.image.relative_path) : undefined} target="_blank" rel="noreferrer">
|
<div className="wallMedia">
|
||||||
{row.image?.name ?? "-"}
|
<DatasetWallPane item={row.image} label="image" emptyText={row.status === "extra_mask" ? "无原图" : "无图片"} />
|
||||||
</a>
|
<DatasetWallPane item={row.mask} label="mask" emptyText={row.status === "missing_mask" ? "缺少 mask" : "无 mask"} />
|
||||||
<span>{row.image ? formatBytes(row.image.size) : "-"}</span>
|
|
||||||
<a href={row.mask ? artifactUrl(row.mask.relative_path) : undefined} target="_blank" rel="noreferrer">
|
|
||||||
{row.mask?.name ?? "-"}
|
|
||||||
</a>
|
|
||||||
<span>{row.mask ? formatBytes(row.mask.size) : "-"}</span>
|
|
||||||
<strong>{row.status === "paired" ? "已配对" : row.status === "missing_mask" ? "缺少 mask" : "多余 mask"}</strong>
|
|
||||||
</div>
|
</div>
|
||||||
|
<div className="wallCaption">
|
||||||
|
<strong>{row.stem}</strong>
|
||||||
|
<span>{row.image?.name ?? "-"} / {row.mask?.name ?? "-"}</span>
|
||||||
|
<small>{row.image ? formatBytes(row.image.size) : "-"} · {row.mask ? formatBytes(row.mask.size) : "-"}</small>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
)) : (
|
)) : (
|
||||||
<p className="muted">暂无文件。</p>
|
<p className="muted">暂无文件。</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="issueGrid">
|
{(problemRows.length > 0 || shapeErrors.length > 0) && (
|
||||||
<div>
|
<div className="wallIssueRail">
|
||||||
<strong>多余或无法匹配</strong>
|
{problemRows.slice(0, 80).map((row) => (
|
||||||
{problemRows.length ? problemRows.slice(0, 80).map((row) => (
|
<span key={row.key}>{row.status === "missing_mask" ? `缺少 mask: ${row.image?.name}` : `多余 mask: ${row.mask?.name}`}</span>
|
||||||
<small key={row.key}>{row.status === "missing_mask" ? row.image?.name : row.mask?.name}</small>
|
))}
|
||||||
)) : <small>当前没有未配对文件。</small>}
|
{shapeErrors.slice(0, 20).map((item, index) => (
|
||||||
|
<span key={`shape-${index}`}>尺寸问题: {JSON.stringify(item)}</span>
|
||||||
|
))}
|
||||||
</div>
|
</div>
|
||||||
<div>
|
)}
|
||||||
<strong>尺寸问题</strong>
|
|
||||||
{shapeErrors.length ? shapeErrors.slice(0, 20).map((item, index) => (
|
|
||||||
<small key={index}>{JSON.stringify(item)}</small>
|
|
||||||
)) : <small>当前抽样未发现 image/mask 尺寸不一致。</small>}
|
|
||||||
</div>
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function DatasetWallPane({ item, label, emptyText }: { item?: DatasetFileItem; label: string; emptyText: string }) {
|
||||||
|
if (!item) {
|
||||||
|
return (
|
||||||
|
<div className="wallPane emptyPane">
|
||||||
|
<FileImage size={24} />
|
||||||
|
<span>{emptyText}</span>
|
||||||
</div>
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<a className="wallPane" href={artifactUrl(item.relative_path)} target="_blank" rel="noreferrer">
|
||||||
|
{item.previewable ? <img src={artifactUrl(item.relative_path)} alt={item.name} /> : (
|
||||||
|
<div className="mediaPlaceholder">
|
||||||
|
<FileImage size={26} />
|
||||||
|
<span>{item.media_type ?? "file"}</span>
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
|
<i>{label}</i>
|
||||||
|
</a>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -740,6 +740,12 @@ textarea {
|
|||||||
gap: 16px;
|
gap: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.addDatasetButton {
|
||||||
|
color: #0b0d0b;
|
||||||
|
background: var(--green);
|
||||||
|
border-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
.datasetManager,
|
.datasetManager,
|
||||||
.preprocessBox,
|
.preprocessBox,
|
||||||
.datasetDetailStack {
|
.datasetDetailStack {
|
||||||
@@ -759,6 +765,44 @@ textarea {
|
|||||||
line-height: 1.35;
|
line-height: 1.35;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.modalOverlay {
|
||||||
|
position: fixed;
|
||||||
|
inset: 0;
|
||||||
|
z-index: 50;
|
||||||
|
display: grid;
|
||||||
|
place-items: center;
|
||||||
|
padding: 28px;
|
||||||
|
background: rgba(4, 7, 5, 0.72);
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.datasetUploadModal {
|
||||||
|
width: min(620px, 100%);
|
||||||
|
max-height: calc(100vh - 56px);
|
||||||
|
overflow: auto;
|
||||||
|
padding: 18px;
|
||||||
|
border: 1px solid rgba(157, 226, 111, 0.32);
|
||||||
|
border-radius: 8px;
|
||||||
|
background:
|
||||||
|
linear-gradient(135deg, rgba(157, 226, 111, 0.06), transparent 42%),
|
||||||
|
#151815;
|
||||||
|
box-shadow: 0 28px 80px rgba(0, 0, 0, 0.55);
|
||||||
|
}
|
||||||
|
|
||||||
|
.modalHead {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 16px;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modalActions {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
.segmented {
|
.segmented {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(2, 1fr);
|
grid-template-columns: repeat(2, 1fr);
|
||||||
@@ -960,6 +1004,177 @@ textarea {
|
|||||||
font-size: 20px;
|
font-size: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.datasetWallStats {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.datasetWallStats span,
|
||||||
|
.datasetWallStats strong {
|
||||||
|
padding: 7px 9px;
|
||||||
|
border: 1px solid var(--line);
|
||||||
|
border-radius: 999px;
|
||||||
|
background: #101310;
|
||||||
|
color: var(--muted);
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.datasetWallStats strong {
|
||||||
|
color: var(--green);
|
||||||
|
border-color: rgba(157, 226, 111, 0.35);
|
||||||
|
}
|
||||||
|
|
||||||
|
.imageWall {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(210px, 1fr));
|
||||||
|
gap: 12px;
|
||||||
|
align-items: start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wallCard {
|
||||||
|
position: relative;
|
||||||
|
min-width: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
border: 1px solid var(--line);
|
||||||
|
border-radius: 8px;
|
||||||
|
background: #101310;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wallCard.paired {
|
||||||
|
border-color: rgba(157, 226, 111, 0.28);
|
||||||
|
}
|
||||||
|
|
||||||
|
.wallCard.missing_mask,
|
||||||
|
.wallCard.extra_mask {
|
||||||
|
border-color: rgba(211, 179, 91, 0.52);
|
||||||
|
}
|
||||||
|
|
||||||
|
.wallBadge {
|
||||||
|
position: absolute;
|
||||||
|
top: 8px;
|
||||||
|
right: 8px;
|
||||||
|
z-index: 2;
|
||||||
|
max-width: calc(100% - 16px);
|
||||||
|
padding: 4px 7px;
|
||||||
|
border-radius: 999px;
|
||||||
|
background: rgba(8, 10, 8, 0.78);
|
||||||
|
color: var(--green);
|
||||||
|
border: 1px solid rgba(157, 226, 111, 0.38);
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 760;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wallCard.missing_mask .wallBadge,
|
||||||
|
.wallCard.extra_mask .wallBadge {
|
||||||
|
color: var(--amber);
|
||||||
|
border-color: rgba(211, 179, 91, 0.55);
|
||||||
|
}
|
||||||
|
|
||||||
|
.wallMedia {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||||
|
gap: 1px;
|
||||||
|
background: var(--line);
|
||||||
|
}
|
||||||
|
|
||||||
|
.wallPane {
|
||||||
|
position: relative;
|
||||||
|
min-width: 0;
|
||||||
|
display: grid;
|
||||||
|
place-items: center;
|
||||||
|
aspect-ratio: 1 / 0.82;
|
||||||
|
background: #070907;
|
||||||
|
color: var(--muted);
|
||||||
|
text-decoration: none;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wallPane img {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wallPane i {
|
||||||
|
position: absolute;
|
||||||
|
left: 7px;
|
||||||
|
bottom: 7px;
|
||||||
|
padding: 3px 5px;
|
||||||
|
border-radius: 4px;
|
||||||
|
background: rgba(8, 10, 8, 0.75);
|
||||||
|
color: var(--green);
|
||||||
|
font-style: normal;
|
||||||
|
font-size: 10px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
.emptyPane {
|
||||||
|
gap: 6px;
|
||||||
|
border: 1px dashed rgba(211, 179, 91, 0.45);
|
||||||
|
color: var(--amber);
|
||||||
|
}
|
||||||
|
|
||||||
|
.emptyPane span {
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mediaPlaceholder {
|
||||||
|
display: grid;
|
||||||
|
place-items: center;
|
||||||
|
gap: 5px;
|
||||||
|
color: var(--muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
.mediaPlaceholder span {
|
||||||
|
font-size: 11px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wallCaption {
|
||||||
|
display: grid;
|
||||||
|
gap: 3px;
|
||||||
|
padding: 9px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wallCaption strong,
|
||||||
|
.wallCaption span,
|
||||||
|
.wallCaption small {
|
||||||
|
min-width: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wallCaption span,
|
||||||
|
.wallCaption small {
|
||||||
|
color: var(--muted);
|
||||||
|
font-size: 11px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wallIssueRail {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 7px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wallIssueRail span {
|
||||||
|
max-width: 280px;
|
||||||
|
padding: 6px 8px;
|
||||||
|
border: 1px solid rgba(211, 179, 91, 0.45);
|
||||||
|
border-radius: 999px;
|
||||||
|
background: rgba(211, 179, 91, 0.08);
|
||||||
|
color: var(--amber);
|
||||||
|
font-size: 11px;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
.datasetPreviewBand {
|
.datasetPreviewBand {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(8, minmax(0, 1fr));
|
grid-template-columns: repeat(8, minmax(0, 1fr));
|
||||||
@@ -2189,11 +2404,20 @@ meter {
|
|||||||
.inferencePreview,
|
.inferencePreview,
|
||||||
.datasetSummaryStrip,
|
.datasetSummaryStrip,
|
||||||
.datasetPreviewBand,
|
.datasetPreviewBand,
|
||||||
|
.imageWall,
|
||||||
.issueGrid,
|
.issueGrid,
|
||||||
.userAgentDataset {
|
.userAgentDataset {
|
||||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.modalActions {
|
||||||
|
justify-content: stretch;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modalActions .primary {
|
||||||
|
flex: 1 1 0;
|
||||||
|
}
|
||||||
|
|
||||||
.fileTableHead,
|
.fileTableHead,
|
||||||
.fileTableRow {
|
.fileTableRow {
|
||||||
grid-template-columns: minmax(80px, 0.8fr) minmax(120px, 1.3fr) 74px minmax(120px, 1.3fr) 74px 82px;
|
grid-template-columns: minmax(80px, 0.8fr) minmax(120px, 1.3fr) 74px minmax(120px, 1.3fr) 74px 82px;
|
||||||
@@ -2254,6 +2478,7 @@ meter {
|
|||||||
.opGrid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
|
.opGrid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
|
||||||
.datasetWorkbench { grid-template-columns: 1fr; }
|
.datasetWorkbench { grid-template-columns: 1fr; }
|
||||||
.datasetPreviewBand { grid-template-columns: repeat(4, minmax(0, 1fr)); }
|
.datasetPreviewBand { grid-template-columns: repeat(4, minmax(0, 1fr)); }
|
||||||
|
.imageWall { grid-template-columns: repeat(3, minmax(0, 1fr)); }
|
||||||
.coverageGrid,
|
.coverageGrid,
|
||||||
.taskCheckList { grid-template-columns: 1fr; }
|
.taskCheckList { grid-template-columns: 1fr; }
|
||||||
.grid.three { grid-template-columns: 1fr; }
|
.grid.three { grid-template-columns: 1fr; }
|
||||||
|
|||||||
Reference in New Issue
Block a user