feat(organize,web): sidecars follow selected videos; /console/organize?ids= prefill (FE-4 follow-ups)
- planImport (sourcePaths mode): when a selected source is a video file, same-directory subtitles / external audio whose stem (language tags stripped) matches it join the source list, so the organize wizard, which expands a directory into individual videos, no longer drops them. Unrelated sidecars stay out; pathGuard applies to the added files. - Organize records page honours ?ids= (wizard multi-group redirect): shows just those records via /files/records, refreshes while running, clear chip. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
363f85b1a1
commit
2ae780d2a1
5 files changed
+310
-7
No files matched your search
@@ -22,6 +22,7 @@ import {
|
|||||||
type PathBinding as DomainBinding,
|
type PathBinding as DomainBinding,
|
||||||
type ExistingPolicy,
|
type ExistingPolicy,
|
||||||
type ExistingTarget,
|
type ExistingTarget,
|
||||||
|
fileKindOf,
|
||||||
findOtherVersions,
|
findOtherVersions,
|
||||||
type NewBinding,
|
type NewBinding,
|
||||||
type OrganizePlan,
|
type OrganizePlan,
|
||||||
@@ -32,6 +33,7 @@ import {
|
|||||||
type QualityFacts,
|
type QualityFacts,
|
||||||
resolveTargetDirectory,
|
resolveTargetDirectory,
|
||||||
type SourceFile,
|
type SourceFile,
|
||||||
|
sidecarStem,
|
||||||
} from "@umt/domain";
|
} from "@umt/domain";
|
||||||
import { organizationStatusFromFiles, organizePlanToFileResults, toDomainPathBinding } from "@umt/mapping";
|
import { organizationStatusFromFiles, organizePlanToFileResults, toDomainPathBinding } from "@umt/mapping";
|
||||||
import { withAttemptToken } from "@umt/tasks";
|
import { withAttemptToken } from "@umt/tasks";
|
||||||
@@ -192,6 +194,48 @@ async function walk(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 选中的是单个视频文件时,把同目录里主干名(剥掉 .chs/.eng 等语言标签后)相同的字幕 / 外挂音轨补进源文件列表;
|
||||||
|
* 配对规则与 planOrganize 的附属文件一致(同目录、主干唯一匹配),这里只负责把它们带进来。
|
||||||
|
* 目录源已由 walk 递归带上附属文件;已在列表里的不重复加。
|
||||||
|
*/
|
||||||
|
async function addSidecarSiblings(
|
||||||
|
sources: string[],
|
||||||
|
out: string[],
|
||||||
|
limit: number,
|
||||||
|
guard?: ((p: string) => Promise<string>) | undefined,
|
||||||
|
): Promise<void> {
|
||||||
|
const have = new Set(out);
|
||||||
|
const byDir = new Map<string, Set<string>>();
|
||||||
|
for (const src of sources) {
|
||||||
|
if (fileKindOf(src) !== "video" || !have.has(src)) continue;
|
||||||
|
const dir = dirname(src);
|
||||||
|
const stems = byDir.get(dir) ?? new Set<string>();
|
||||||
|
stems.add(
|
||||||
|
basename(src)
|
||||||
|
.replace(/\.[^.]+$/, "")
|
||||||
|
.toLowerCase(),
|
||||||
|
);
|
||||||
|
byDir.set(dir, stems);
|
||||||
|
}
|
||||||
|
for (const [dir, stems] of byDir) {
|
||||||
|
const names = (await readdir(dir).catch(() => [] as string[])).sort();
|
||||||
|
for (const n of names) {
|
||||||
|
if (out.length >= limit) return;
|
||||||
|
if (n.startsWith(".")) continue;
|
||||||
|
const kind = fileKindOf(n);
|
||||||
|
if (kind !== "subtitle" && kind !== "audio") continue;
|
||||||
|
const p = join(dir, n);
|
||||||
|
if (have.has(p) || !stems.has(sidecarStem(n).stem)) continue;
|
||||||
|
const st = await statSafe(p);
|
||||||
|
if (st.kind !== "ok" || !st.facts.isFile) continue;
|
||||||
|
if (guard) await guard(p);
|
||||||
|
out.push(p);
|
||||||
|
have.add(p);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function libraryKinds(kind: MediaEntityRow["kind"]): string[] {
|
function libraryKinds(kind: MediaEntityRow["kind"]): string[] {
|
||||||
if (kind === "movie") return ["movie", "mixed"];
|
if (kind === "movie") return ["movie", "mixed"];
|
||||||
if (kind === "variety") return ["variety", "series", "mixed"];
|
if (kind === "variety") return ["variety", "series", "mixed"];
|
||||||
@@ -302,6 +346,8 @@ export async function planImport(d: AoDeps, req: ImportRequest): Promise<PlanRes
|
|||||||
try {
|
try {
|
||||||
// 多走一个用来判断是否超限
|
// 多走一个用来判断是否超限
|
||||||
for (const p of req.sourcePaths) await walk(p, paths, max + 1, req.pathGuard);
|
for (const p of req.sourcePaths) await walk(p, paths, max + 1, req.pathGuard);
|
||||||
|
// 直接选中的视频文件(整理向导把目录展开成逐个视频):同目录同主干的字幕 / 外挂音轨随行进计划
|
||||||
|
await addSidecarSiblings(req.sourcePaths, paths, max + 1, req.pathGuard);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (err instanceof PathGuardError) throw err;
|
if (err instanceof PathGuardError) throw err;
|
||||||
return fail("source_missing", { message: errMessage(err) });
|
return fail("source_missing", { message: errMessage(err) });
|
||||||
|
|||||||
@@ -176,6 +176,38 @@ describe.skipIf(skip)("files 识别 / 整理 / 探测 / 刮削", () => {
|
|||||||
await h.db.updateTable("automation_grant").set({ status: "revoked" }).where("id", "=", grant.id).execute();
|
await h.db.updateTable("automation_grant").set({ status: "revoked" }).where("id", "=", grant.id).execute();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("organize:选中的视频带上同目录同主干的字幕 / 外挂音轨,无关字幕不带", async () => {
|
||||||
|
const dir = join(h.dl, "Some.Show.S01.1080p.WEB-DL");
|
||||||
|
await writeFile(join(dir, "Some.Show.S01E02.1080p.WEB-DL-GRP.chs.ass"), "sub-ep2");
|
||||||
|
await writeFile(join(dir, "Some.Show.S01E03.1080p.WEB-DL-GRP.mka"), "audio-ep3");
|
||||||
|
await writeFile(join(dir, "unrelated.srt"), "noise");
|
||||||
|
// 整理向导把目录展开成逐个视频后提交
|
||||||
|
const items = ["Some.Show.S01E02.1080p.WEB-DL-GRP.mkv", "Some.Show.S01E03.1080p.WEB-DL-GRP.mkv"].map((n) => ({
|
||||||
|
root: h.dlRoot,
|
||||||
|
path: `Some.Show.S01.1080p.WEB-DL/${n}`,
|
||||||
|
mediaEntityId: show,
|
||||||
|
season: 1,
|
||||||
|
}));
|
||||||
|
const dry = await h.api("POST", "/files/organize", {
|
||||||
|
idempotencyKey: key(),
|
||||||
|
items,
|
||||||
|
action: "copy",
|
||||||
|
dryRun: true,
|
||||||
|
minSizeBytes: 0,
|
||||||
|
});
|
||||||
|
expect(dry.status).toBe(200);
|
||||||
|
const g = dry.body.data.groups[0];
|
||||||
|
expect(g.error).toBeNull();
|
||||||
|
const byKind = (k: string) =>
|
||||||
|
g.files.filter((f: { kind: string }) => f.kind === k).map((f: { source: string; target: string }) => f);
|
||||||
|
expect(byKind("subtitle")).toHaveLength(1);
|
||||||
|
expect(byKind("subtitle")[0].source).toBe(join(dir, "Some.Show.S01E02.1080p.WEB-DL-GRP.chs.ass"));
|
||||||
|
expect(byKind("subtitle")[0].target).toMatch(/S01E02.*\.chi\.zh-cn\.ass$/);
|
||||||
|
expect(byKind("audio")).toHaveLength(1);
|
||||||
|
expect(byKind("audio")[0].target).toMatch(/S01E03.*\.mka$/);
|
||||||
|
expect(JSON.stringify(g)).not.toContain("unrelated.srt");
|
||||||
|
});
|
||||||
|
|
||||||
it("organize:路径逃逸同样拒绝", async () => {
|
it("organize:路径逃逸同样拒绝", async () => {
|
||||||
const r = await h.api("POST", "/files/organize", {
|
const r = await h.api("POST", "/files/organize", {
|
||||||
idempotencyKey: key(),
|
idempotencyKey: key(),
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { EventType } from "@umt/contracts";
|
|||||||
import { FileStack, FolderInput, X } from "lucide-react";
|
import { FileStack, FolderInput, X } from "lucide-react";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { useNavigate, useSearchParams } from "react-router";
|
import { useNavigate, useSearchParams } from "react-router";
|
||||||
|
import { useFileRecords } from "~/api/files.ts";
|
||||||
import { RECORD_STATUS_LABEL, RECORD_STATUS_TONE, TRIGGER_LABEL, useRecords } from "~/api/records.ts";
|
import { RECORD_STATUS_LABEL, RECORD_STATUS_TONE, TRIGGER_LABEL, useRecords } from "~/api/records.ts";
|
||||||
import { type DataColumn, DataTable } from "~/components/console/data-table.tsx";
|
import { type DataColumn, DataTable } from "~/components/console/data-table.tsx";
|
||||||
import { FilterBar, FilterSelect } from "~/components/console/filter-bar.tsx";
|
import { FilterBar, FilterSelect } from "~/components/console/filter-bar.tsx";
|
||||||
@@ -89,6 +90,12 @@ export default function OrganizePage() {
|
|||||||
const [qInput, setQInput] = useState("");
|
const [qInput, setQInput] = useState("");
|
||||||
const [q, setQ] = useState("");
|
const [q, setQ] = useState("");
|
||||||
const entityId = params.get("mediaEntityId") ?? undefined;
|
const entityId = params.get("mediaEntityId") ?? undefined;
|
||||||
|
// 整理向导执行多组后跳来:?ids=<记录 id,…> 只看这几条(执行中每 3 秒刷新)
|
||||||
|
const ids = (params.get("ids") ?? "")
|
||||||
|
.split(",")
|
||||||
|
.map((x) => x.trim())
|
||||||
|
.filter(Boolean);
|
||||||
|
const byIds = useFileRecords({ ids }, ids.length > 0);
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const qc = useQueryClient();
|
const qc = useQueryClient();
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -101,9 +108,13 @@ export default function OrganizePage() {
|
|||||||
mediaEntityId: entityId,
|
mediaEntityId: entityId,
|
||||||
q: q || undefined,
|
q: q || undefined,
|
||||||
});
|
});
|
||||||
useSse([EventType.organizationChanged], () => void qc.invalidateQueries({ queryKey: ["records"] }));
|
useSse([EventType.organizationChanged], () => {
|
||||||
const rows = records.data?.pages.flatMap((p) => p.items);
|
void qc.invalidateQueries({ queryKey: ["records"] });
|
||||||
const filtered = !!(status || trigger || q || entityId);
|
if (ids.length) void qc.invalidateQueries({ queryKey: ["files", "records"] });
|
||||||
|
});
|
||||||
|
const pinned = ids.length > 0;
|
||||||
|
const rows = pinned ? byIds.data?.items : records.data?.pages.flatMap((p) => p.items);
|
||||||
|
const filtered = !!(status || trigger || q || entityId || pinned);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -148,16 +159,22 @@ export default function OrganizePage() {
|
|||||||
<X />
|
<X />
|
||||||
</Button>
|
</Button>
|
||||||
) : null}
|
) : null}
|
||||||
|
{pinned ? (
|
||||||
|
<Button variant="outline" size="sm" onClick={() => setParams({})}>
|
||||||
|
本次整理的 {ids.length} 条
|
||||||
|
<X />
|
||||||
|
</Button>
|
||||||
|
) : null}
|
||||||
</FilterBar>
|
</FilterBar>
|
||||||
<DataTable
|
<DataTable
|
||||||
columns={COLUMNS}
|
columns={COLUMNS}
|
||||||
rows={rows}
|
rows={rows}
|
||||||
rowKey={(r) => r.id}
|
rowKey={(r) => r.id}
|
||||||
loading={records.isPending}
|
loading={pinned ? byIds.isPending : records.isPending}
|
||||||
error={records.error}
|
error={pinned ? byIds.error : records.error}
|
||||||
onRetry={() => void records.refetch()}
|
onRetry={() => void (pinned ? byIds.refetch() : records.refetch())}
|
||||||
onRowClick={(r) => navigate(`/console/organize/${r.id}`)}
|
onRowClick={(r) => navigate(`/console/organize/${r.id}`)}
|
||||||
hasMore={records.hasNextPage}
|
hasMore={!pinned && records.hasNextPage}
|
||||||
onLoadMore={() => void records.fetchNextPage()}
|
onLoadMore={() => void records.fetchNextPage()}
|
||||||
loadingMore={records.isFetchingNextPage}
|
loadingMore={records.isFetchingNextPage}
|
||||||
empty={{
|
empty={{
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ const STATIC_ROUTES = [
|
|||||||
"/console/library?filter=unidentified",
|
"/console/library?filter=unidentified",
|
||||||
"/console/library?view=identities",
|
"/console/library?view=identities",
|
||||||
"/console/organize",
|
"/console/organize",
|
||||||
|
"/console/organize?ids=00000000-0000-4000-8000-000000000000",
|
||||||
"/console/files",
|
"/console/files",
|
||||||
// 文件管理 v2(FE-4):筛选/排序组合与演示数据(?mock=1 只在开发构建生效,随后 ?mock=0 关掉,免得影响后面的页)
|
// 文件管理 v2(FE-4):筛选/排序组合与演示数据(?mock=1 只在开发构建生效,随后 ?mock=0 关掉,免得影响后面的页)
|
||||||
"/console/files?f=video&sort=size&order=desc&hidden=1",
|
"/console/files?f=video&sort=size&order=desc&hidden=1",
|
||||||
|
|||||||
@@ -45,6 +45,7 @@
|
|||||||
"npm:react-dom@^19.2.4": "[email protected]",
|
"npm:react-dom@^19.2.4": "[email protected]",
|
||||||
"npm:[email protected]": "[email protected][email protected]",
|
"npm:[email protected]": "[email protected][email protected]",
|
||||||
"npm:react@^19.2.4": "19.3.0",
|
"npm:react@^19.2.4": "19.3.0",
|
||||||
|
"npm:recharts@^3.10.1": "[email protected][email protected][email protected]_@[email protected][email protected]",
|
||||||
"npm:shadcn@^3.8.5": "3.8.5_@[email protected][email protected]",
|
"npm:shadcn@^3.8.5": "3.8.5_@[email protected][email protected]",
|
||||||
"npm:sonner@^2.0.7": "2.0.8_@[email protected][email protected][email protected]",
|
"npm:sonner@^2.0.7": "2.0.8_@[email protected][email protected][email protected]",
|
||||||
"npm:tailwind-merge@^3.5.0": "3.7.0",
|
"npm:tailwind-merge@^3.5.0": "3.7.0",
|
||||||
@@ -2110,6 +2111,23 @@
|
|||||||
"typescript"
|
"typescript"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"@reduxjs/[email protected][email protected][email protected]_@[email protected]": {
|
||||||
|
"integrity": "sha512-KiT+RzZbp6mQET+Mg+h2c97+9j1sNflUxQkIHI7Yuzf6Peu+OYpmkn6nbHWmLLWj+1ZODUJFwGZ7gx3L9R9EOw==",
|
||||||
|
"dependencies": [
|
||||||
|
"@standard-schema/spec",
|
||||||
|
"@standard-schema/utils",
|
||||||
|
"immer",
|
||||||
|
"react",
|
||||||
|
"react-redux",
|
||||||
|
"redux",
|
||||||
|
"redux-thunk",
|
||||||
|
"reselect"
|
||||||
|
],
|
||||||
|
"optionalPeers": [
|
||||||
|
"react",
|
||||||
|
"react-redux"
|
||||||
|
]
|
||||||
|
},
|
||||||
"@remix-run/[email protected]": {
|
"@remix-run/[email protected]": {
|
||||||
"integrity": "sha512-SoLMv7dbH+njWzXnOY6fI08dFMI5+/dQ+vY3n8RnnbdG7MdJEgiP28Xj/xWlnRnED/aB6SFw56Zop+LbmaaKqA=="
|
"integrity": "sha512-SoLMv7dbH+njWzXnOY6fI08dFMI5+/dQ+vY3n8RnnbdG7MdJEgiP28Xj/xWlnRnED/aB6SFw56Zop+LbmaaKqA=="
|
||||||
},
|
},
|
||||||
@@ -2292,6 +2310,12 @@
|
|||||||
"@stablelib/[email protected]": {
|
"@stablelib/[email protected]": {
|
||||||
"integrity": "sha512-1bnPQqSxSuc3Ii6MhBysoWCg58j97aUjuCSZrGSmDxNqtytIi0k8utUenAwTZN4V5mXXYGsVUI9zeBqy+jBOSQ=="
|
"integrity": "sha512-1bnPQqSxSuc3Ii6MhBysoWCg58j97aUjuCSZrGSmDxNqtytIi0k8utUenAwTZN4V5mXXYGsVUI9zeBqy+jBOSQ=="
|
||||||
},
|
},
|
||||||
|
"@standard-schema/[email protected]": {
|
||||||
|
"integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="
|
||||||
|
},
|
||||||
|
"@standard-schema/[email protected]": {
|
||||||
|
"integrity": "sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g=="
|
||||||
|
},
|
||||||
"@tailwindcss/[email protected]": {
|
"@tailwindcss/[email protected]": {
|
||||||
"integrity": "sha512-/T8IKEsf9VTU6tLjgC7+sv2mOPtQxzE2jMw7u4Tt40Tx+QSZxpzh95/H6cMKoja9XuW7iMdLJYBB0o9G1CaAgg==",
|
"integrity": "sha512-/T8IKEsf9VTU6tLjgC7+sv2mOPtQxzE2jMw7u4Tt40Tx+QSZxpzh95/H6cMKoja9XuW7iMdLJYBB0o9G1CaAgg==",
|
||||||
"dependencies": [
|
"dependencies": [
|
||||||
@@ -2414,6 +2438,42 @@
|
|||||||
"assertion-error"
|
"assertion-error"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"@types/[email protected]": {
|
||||||
|
"integrity": "sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw=="
|
||||||
|
},
|
||||||
|
"@types/[email protected]": {
|
||||||
|
"integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A=="
|
||||||
|
},
|
||||||
|
"@types/[email protected]": {
|
||||||
|
"integrity": "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA=="
|
||||||
|
},
|
||||||
|
"@types/[email protected]": {
|
||||||
|
"integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==",
|
||||||
|
"dependencies": [
|
||||||
|
"@types/d3-color"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"@types/[email protected]": {
|
||||||
|
"integrity": "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg=="
|
||||||
|
},
|
||||||
|
"@types/[email protected]": {
|
||||||
|
"integrity": "sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==",
|
||||||
|
"dependencies": [
|
||||||
|
"@types/d3-time"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"@types/[email protected]": {
|
||||||
|
"integrity": "sha512-kVd74ta9eof3eJOvbNd1vGKS/XERRyQbT26Og63hIsvDO84cjD5gEOhsXf26w3FSoNlPVz84DOFcKv/oou+fMw==",
|
||||||
|
"dependencies": [
|
||||||
|
"@types/d3-path"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"@types/[email protected]": {
|
||||||
|
"integrity": "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g=="
|
||||||
|
},
|
||||||
|
"@types/[email protected]": {
|
||||||
|
"integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw=="
|
||||||
|
},
|
||||||
"@types/[email protected]": {
|
"@types/[email protected]": {
|
||||||
"integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw=="
|
"integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw=="
|
||||||
},
|
},
|
||||||
@@ -2461,6 +2521,9 @@
|
|||||||
"@types/[email protected]": {
|
"@types/[email protected]": {
|
||||||
"integrity": "sha512-xMAgYwceFhRA2zY+XbEA7mxYbA093wdiW8Vu6gZPGWy9cmOyU9XesH1tNcEWsKFd5Vzrqx5T3D38PWx1FIIXkA=="
|
"integrity": "sha512-xMAgYwceFhRA2zY+XbEA7mxYbA093wdiW8Vu6gZPGWy9cmOyU9XesH1tNcEWsKFd5Vzrqx5T3D38PWx1FIIXkA=="
|
||||||
},
|
},
|
||||||
|
"@types/[email protected]": {
|
||||||
|
"integrity": "sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg=="
|
||||||
|
},
|
||||||
"@types/[email protected]": {
|
"@types/[email protected]": {
|
||||||
"integrity": "sha512-lrpDziQipxCEeK5kWxvljWYhUvOiB2A9izZd9B2AFarYAkqZshb4lPbRs7zKEic6eGtH8V/2qJW+dPp9OtF6bw=="
|
"integrity": "sha512-lrpDziQipxCEeK5kWxvljWYhUvOiB2A9izZd9B2AFarYAkqZshb4lPbRs7zKEic6eGtH8V/2qJW+dPp9OtF6bw=="
|
||||||
},
|
},
|
||||||
@@ -2956,6 +3019,61 @@
|
|||||||
"[email protected]": {
|
"[email protected]": {
|
||||||
"integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="
|
"integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="
|
||||||
},
|
},
|
||||||
|
"[email protected]": {
|
||||||
|
"integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==",
|
||||||
|
"dependencies": [
|
||||||
|
"internmap"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"[email protected]": {
|
||||||
|
"integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA=="
|
||||||
|
},
|
||||||
|
"[email protected]": {
|
||||||
|
"integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w=="
|
||||||
|
},
|
||||||
|
"[email protected]": {
|
||||||
|
"integrity": "sha512-AJDdYOdnyRDV5b6ArilzCPPwc1ejkHcoyFarqlPqT7zRYjhavcT3uSrqcMvsgh2CgoPbK3RCwyHaVyxYcP2Arg=="
|
||||||
|
},
|
||||||
|
"[email protected]": {
|
||||||
|
"integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==",
|
||||||
|
"dependencies": [
|
||||||
|
"d3-color"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"[email protected]": {
|
||||||
|
"integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ=="
|
||||||
|
},
|
||||||
|
"[email protected]": {
|
||||||
|
"integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==",
|
||||||
|
"dependencies": [
|
||||||
|
"d3-array",
|
||||||
|
"d3-format",
|
||||||
|
"d3-interpolate",
|
||||||
|
"d3-time",
|
||||||
|
"d3-time-format"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"[email protected]": {
|
||||||
|
"integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==",
|
||||||
|
"dependencies": [
|
||||||
|
"d3-path"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"[email protected]": {
|
||||||
|
"integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==",
|
||||||
|
"dependencies": [
|
||||||
|
"d3-time"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"[email protected]": {
|
||||||
|
"integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==",
|
||||||
|
"dependencies": [
|
||||||
|
"d3-array"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"[email protected]": {
|
||||||
|
"integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA=="
|
||||||
|
},
|
||||||
"[email protected]": {
|
"[email protected]": {
|
||||||
"integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A=="
|
"integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A=="
|
||||||
},
|
},
|
||||||
@@ -2977,6 +3095,9 @@
|
|||||||
"ms"
|
"ms"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"[email protected]": {
|
||||||
|
"integrity": "sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg=="
|
||||||
|
},
|
||||||
"[email protected]": {
|
"[email protected]": {
|
||||||
"integrity": "sha512-WzMx3mW98SN+zn3hgemf4OzdmyNhhhKz5Ay0pUfQiMQ3e1g+xmTJWp/pKdwKVXhdSkAEGIIzqeuWrL3mV/AXbA=="
|
"integrity": "sha512-WzMx3mW98SN+zn3hgemf4OzdmyNhhhKz5Ay0pUfQiMQ3e1g+xmTJWp/pKdwKVXhdSkAEGIIzqeuWrL3mV/AXbA=="
|
||||||
},
|
},
|
||||||
@@ -3137,6 +3258,9 @@
|
|||||||
"es-errors"
|
"es-errors"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"[email protected]": {
|
||||||
|
"integrity": "sha512-XTNEJQh1tY1ZJVcf6ayP/2n4ZPyaHlW2FWs7xvw5ddPuhUVjLD3olQVQS7kf58JbAB48iL0uL/jerTrjtV3lDA=="
|
||||||
|
},
|
||||||
"[email protected]": {
|
"[email protected]": {
|
||||||
"integrity": "sha512-HKVLS8dvII+xoKW9kmqxbRKrnWEXfJJr/FZhhJmiqIB0e053QNYFqOBouTMO/k5sID4MvCiUCvv8b9M4h32wIA==",
|
"integrity": "sha512-HKVLS8dvII+xoKW9kmqxbRKrnWEXfJJr/FZhhJmiqIB0e053QNYFqOBouTMO/k5sID4MvCiUCvv8b9M4h32wIA==",
|
||||||
"optionalDependencies": [
|
"optionalDependencies": [
|
||||||
@@ -3189,6 +3313,9 @@
|
|||||||
"[email protected]": {
|
"[email protected]": {
|
||||||
"integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg=="
|
"integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg=="
|
||||||
},
|
},
|
||||||
|
"[email protected]": {
|
||||||
|
"integrity": "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw=="
|
||||||
|
},
|
||||||
"[email protected]": {
|
"[email protected]": {
|
||||||
"integrity": "sha512-EKN1vKAMcZ8MlYMpaNuxN6R9yakzH6uajHcHVTqWJzvu5pWw9DyhbP35HH8MVBQ+dZjAfDxk+A8NiR9KWaXiyQ=="
|
"integrity": "sha512-EKN1vKAMcZ8MlYMpaNuxN6R9yakzH6uajHcHVTqWJzvu5pWw9DyhbP35HH8MVBQ+dZjAfDxk+A8NiR9KWaXiyQ=="
|
||||||
},
|
},
|
||||||
@@ -3612,6 +3739,9 @@
|
|||||||
"[email protected]": {
|
"[email protected]": {
|
||||||
"integrity": "sha512-YYNsSlXBjMk92SKnkwvB5LOVSa6OznlFUGcsvrFgNJbJCd0M1XKeFVRc8ZByeCqz32FivYNHJVooLmdqrmvp/Q=="
|
"integrity": "sha512-YYNsSlXBjMk92SKnkwvB5LOVSa6OznlFUGcsvrFgNJbJCd0M1XKeFVRc8ZByeCqz32FivYNHJVooLmdqrmvp/Q=="
|
||||||
},
|
},
|
||||||
|
"[email protected]": {
|
||||||
|
"integrity": "sha512-EQyQtLiYW029lyoczMl/Hh4Xu7cDecSc58JRYpHyL4tIAu3eqd1yJzQX04d2BZHDkzFFvm6qJEJWOtfDSWAXbQ=="
|
||||||
|
},
|
||||||
"[email protected]": {
|
"[email protected]": {
|
||||||
"integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==",
|
"integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==",
|
||||||
"dependencies": [
|
"dependencies": [
|
||||||
@@ -3622,6 +3752,9 @@
|
|||||||
"[email protected]": {
|
"[email protected]": {
|
||||||
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
|
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
|
||||||
},
|
},
|
||||||
|
"[email protected]": {
|
||||||
|
"integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg=="
|
||||||
|
},
|
||||||
"[email protected]": {
|
"[email protected]": {
|
||||||
"integrity": "sha512-7H/2gFSIitxc0hG3nOI1glS8QLo/EHBFFLk8vEUjXY/xu0AdL8jZ9U1IzO2PUm0d2D/ofQcAifb0g6OBkt8U7w=="
|
"integrity": "sha512-7H/2gFSIitxc0hG3nOI1glS8QLo/EHBFFLk8vEUjXY/xu0AdL8jZ9U1IzO2PUm0d2D/ofQcAifb0g6OBkt8U7w=="
|
||||||
},
|
},
|
||||||
@@ -4596,6 +4729,23 @@
|
|||||||
"scheduler"
|
"scheduler"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"[email protected]": {
|
||||||
|
"integrity": "sha512-UpMYezM4v5/18F28aC66AEsjXIgE02kyEMH6yLdgLXu/UTfa1Ntwck/nNLrbqJsEXW7gPb0coNO9FQse9WTovA=="
|
||||||
|
},
|
||||||
|
"[email protected]_@[email protected][email protected][email protected]": {
|
||||||
|
"integrity": "sha512-KQopgqFo/p/fgmAs5qz6p5RWaNAzq40WAu7fJIXnQpYxFPbJYtsJPWvGeF2rOBaY/kEuV77AVsX8TsQzKm+A/g==",
|
||||||
|
"dependencies": [
|
||||||
|
"@types/react",
|
||||||
|
"@types/use-sync-external-store",
|
||||||
|
"react",
|
||||||
|
"redux",
|
||||||
|
"use-sync-external-store"
|
||||||
|
],
|
||||||
|
"optionalPeers": [
|
||||||
|
"@types/react",
|
||||||
|
"redux"
|
||||||
|
]
|
||||||
|
},
|
||||||
"[email protected]": {
|
"[email protected]": {
|
||||||
"integrity": "sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA=="
|
"integrity": "sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA=="
|
||||||
},
|
},
|
||||||
@@ -4672,12 +4822,43 @@
|
|||||||
"tslib"
|
"tslib"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"[email protected][email protected][email protected][email protected]_@[email protected][email protected]": {
|
||||||
|
"integrity": "sha512-QXFrvt6IVcw7eeZCoyXTwkIJAX3Dv1nyVhMicXJ47GsGDDpcN8z6o644DibE9XjpBTThtsomLKnTV6lc+cVFUA==",
|
||||||
|
"dependencies": [
|
||||||
|
"@reduxjs/toolkit",
|
||||||
|
"clsx",
|
||||||
|
"decimal.js-light",
|
||||||
|
"es-toolkit",
|
||||||
|
"eventemitter3",
|
||||||
|
"immer",
|
||||||
|
"react",
|
||||||
|
"react-dom",
|
||||||
|
"react-is",
|
||||||
|
"react-redux",
|
||||||
|
"reselect",
|
||||||
|
"tiny-invariant",
|
||||||
|
"use-sync-external-store",
|
||||||
|
"victory-vendor"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"[email protected][email protected]": {
|
||||||
|
"integrity": "sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw==",
|
||||||
|
"dependencies": [
|
||||||
|
"redux"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"[email protected]": {
|
||||||
|
"integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w=="
|
||||||
|
},
|
||||||
"[email protected]": {
|
"[email protected]": {
|
||||||
"integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q=="
|
"integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q=="
|
||||||
},
|
},
|
||||||
"[email protected]": {
|
"[email protected]": {
|
||||||
"integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw=="
|
"integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw=="
|
||||||
},
|
},
|
||||||
|
"[email protected]": {
|
||||||
|
"integrity": "sha512-AgZ3UOZm3YndfrJ4OYjgrT7bmCm/1iqkjvEfH/oYjzh6PD2qw4QuT3jjnXIrpdt4MTpMXclMT3lXbmRY+XRakw=="
|
||||||
|
},
|
||||||
"[email protected]": {
|
"[email protected]": {
|
||||||
"integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="
|
"integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="
|
||||||
},
|
},
|
||||||
@@ -5195,6 +5376,12 @@
|
|||||||
"@types/react"
|
"@types/react"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"[email protected][email protected]": {
|
||||||
|
"integrity": "sha512-6L+EeigHMQhdaIPNIFUKwfWJSwWFQ8gJbJ2DLOs5sDIegTwR9fRxvnM3uciHKjIZhFz+KAv2emhWMRvDmMcY8A==",
|
||||||
|
"dependencies": [
|
||||||
|
"react"
|
||||||
|
]
|
||||||
|
},
|
||||||
"[email protected]": {
|
"[email protected]": {
|
||||||
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="
|
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="
|
||||||
},
|
},
|
||||||
@@ -5213,6 +5400,25 @@
|
|||||||
"[email protected]": {
|
"[email protected]": {
|
||||||
"integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg=="
|
"integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg=="
|
||||||
},
|
},
|
||||||
|
"[email protected]": {
|
||||||
|
"integrity": "sha512-SbPDPdDBYp+5MJHhBCAyI7wKM3d5ivekigc2Dk2s7pgbZ9wIgIBYGVw4zGHBml/qTFbexrofXW6Gu4noGxrOwQ==",
|
||||||
|
"dependencies": [
|
||||||
|
"@types/d3-array",
|
||||||
|
"@types/d3-ease",
|
||||||
|
"@types/d3-interpolate",
|
||||||
|
"@types/d3-scale",
|
||||||
|
"@types/d3-shape",
|
||||||
|
"@types/d3-time",
|
||||||
|
"@types/d3-timer",
|
||||||
|
"d3-array",
|
||||||
|
"d3-ease",
|
||||||
|
"d3-interpolate",
|
||||||
|
"d3-scale",
|
||||||
|
"d3-shape",
|
||||||
|
"d3-time",
|
||||||
|
"d3-timer"
|
||||||
|
]
|
||||||
|
},
|
||||||
"[email protected]_@[email protected][email protected]": {
|
"[email protected]_@[email protected][email protected]": {
|
||||||
"integrity": "sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==",
|
"integrity": "sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==",
|
||||||
"dependencies": [
|
"dependencies": [
|
||||||
@@ -5446,6 +5652,7 @@
|
|||||||
"npm:react-dom@^19.2.4",
|
"npm:react-dom@^19.2.4",
|
||||||
"npm:[email protected]",
|
"npm:[email protected]",
|
||||||
"npm:react@^19.2.4",
|
"npm:react@^19.2.4",
|
||||||
|
"npm:recharts@^3.10.1",
|
||||||
"npm:shadcn@^3.8.5",
|
"npm:shadcn@^3.8.5",
|
||||||
"npm:sonner@^2.0.7",
|
"npm:sonner@^2.0.7",
|
||||||
"npm:tailwind-merge@^3.5.0",
|
"npm:tailwind-merge@^3.5.0",
|
||||||
|
|||||||
Reference in new issue
Block a user