feat(libraries): add export (JSON download) and import (JSON restore) for library symbols
This commit is contained in:
parent
75637bc265
commit
914005e00e
@ -1,5 +1,5 @@
|
|||||||
/**
|
/**
|
||||||
* Generated by orval v8.15.0 🍺
|
* Generated by orval v8.20.0 🍺
|
||||||
* Do not edit manually.
|
* Do not edit manually.
|
||||||
* OpenAPI definition
|
* OpenAPI definition
|
||||||
* OpenAPI spec version: v0
|
* OpenAPI spec version: v0
|
||||||
@ -33,6 +33,21 @@ import { customInstance } from "../../api";
|
|||||||
|
|
||||||
type SecondParameter<T extends (...args: never) => unknown> = Parameters<T>[1];
|
type SecondParameter<T extends (...args: never) => unknown> = Parameters<T>[1];
|
||||||
|
|
||||||
|
const withQueryKey = <T extends object, K>(query: T, queryKey: K): T & { queryKey: K } => {
|
||||||
|
const result = { queryKey } as T & { queryKey: K };
|
||||||
|
for (const key of Object.keys(query)) {
|
||||||
|
// The explicit queryKey always wins, matching the previous
|
||||||
|
// `{ ...query, queryKey }` spread where it was set last.
|
||||||
|
if (key === "queryKey") continue;
|
||||||
|
Object.defineProperty(result, key, {
|
||||||
|
enumerable: true,
|
||||||
|
configurable: true,
|
||||||
|
get: () => (query as Record<string, unknown>)[key],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve all static analyses performed on a binary.
|
* Retrieve all static analyses performed on a binary.
|
||||||
* @summary List static analyses for a binary
|
* @summary List static analyses for a binary
|
||||||
@ -140,7 +155,7 @@ export function useListAnalyses<TData = Awaited<ReturnType<typeof listAnalyses>>
|
|||||||
queryKey: DataTag<QueryKey, TData, TError>;
|
queryKey: DataTag<QueryKey, TData, TError>;
|
||||||
};
|
};
|
||||||
|
|
||||||
return { ...query, queryKey: queryOptions.queryKey };
|
return withQueryKey(query, queryOptions.queryKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -250,7 +265,7 @@ export function useGetAnalysis<TData = Awaited<ReturnType<typeof getAnalysis>>,
|
|||||||
queryKey: DataTag<QueryKey, TData, TError>;
|
queryKey: DataTag<QueryKey, TData, TError>;
|
||||||
};
|
};
|
||||||
|
|
||||||
return { ...query, queryKey: queryOptions.queryKey };
|
return withQueryKey(query, queryOptions.queryKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -376,7 +391,7 @@ export function useListStructures<
|
|||||||
queryKey: DataTag<QueryKey, TData, TError>;
|
queryKey: DataTag<QueryKey, TData, TError>;
|
||||||
};
|
};
|
||||||
|
|
||||||
return { ...query, queryKey: queryOptions.queryKey };
|
return withQueryKey(query, queryOptions.queryKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -486,7 +501,7 @@ export function useListStrings<TData = Awaited<ReturnType<typeof listStrings>>,
|
|||||||
queryKey: DataTag<QueryKey, TData, TError>;
|
queryKey: DataTag<QueryKey, TData, TError>;
|
||||||
};
|
};
|
||||||
|
|
||||||
return { ...query, queryKey: queryOptions.queryKey };
|
return withQueryKey(query, queryOptions.queryKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -596,7 +611,7 @@ export function useListSegments<TData = Awaited<ReturnType<typeof listSegments>>
|
|||||||
queryKey: DataTag<QueryKey, TData, TError>;
|
queryKey: DataTag<QueryKey, TData, TError>;
|
||||||
};
|
};
|
||||||
|
|
||||||
return { ...query, queryKey: queryOptions.queryKey };
|
return withQueryKey(query, queryOptions.queryKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -706,7 +721,7 @@ export function useListImports<TData = Awaited<ReturnType<typeof listImports>>,
|
|||||||
queryKey: DataTag<QueryKey, TData, TError>;
|
queryKey: DataTag<QueryKey, TData, TError>;
|
||||||
};
|
};
|
||||||
|
|
||||||
return { ...query, queryKey: queryOptions.queryKey };
|
return withQueryKey(query, queryOptions.queryKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -832,7 +847,7 @@ export function useListGlobalVars<
|
|||||||
queryKey: DataTag<QueryKey, TData, TError>;
|
queryKey: DataTag<QueryKey, TData, TError>;
|
||||||
};
|
};
|
||||||
|
|
||||||
return { ...query, queryKey: queryOptions.queryKey };
|
return withQueryKey(query, queryOptions.queryKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -965,7 +980,7 @@ export function useListFunctions<
|
|||||||
queryKey: DataTag<QueryKey, TData, TError>;
|
queryKey: DataTag<QueryKey, TData, TError>;
|
||||||
};
|
};
|
||||||
|
|
||||||
return { ...query, queryKey: queryOptions.queryKey };
|
return withQueryKey(query, queryOptions.queryKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -1075,5 +1090,5 @@ export function useListEnums<TData = Awaited<ReturnType<typeof listEnums>>, TErr
|
|||||||
queryKey: DataTag<QueryKey, TData, TError>;
|
queryKey: DataTag<QueryKey, TData, TError>;
|
||||||
};
|
};
|
||||||
|
|
||||||
return { ...query, queryKey: queryOptions.queryKey };
|
return withQueryKey(query, queryOptions.queryKey);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
/**
|
/**
|
||||||
* Generated by orval v8.15.0 🍺
|
* Generated by orval v8.20.0 🍺
|
||||||
* Do not edit manually.
|
* Do not edit manually.
|
||||||
* OpenAPI definition
|
* OpenAPI definition
|
||||||
* OpenAPI spec version: v0
|
* OpenAPI spec version: v0
|
||||||
@ -84,6 +84,23 @@ export interface LibraryResponse {
|
|||||||
createdAt?: string;
|
createdAt?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface SymbolEntry {
|
||||||
|
moduleName?: string;
|
||||||
|
className?: string;
|
||||||
|
symbolName?: string;
|
||||||
|
ordinal?: number;
|
||||||
|
symbolType?: string;
|
||||||
|
parameters?: string;
|
||||||
|
category?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Library {
|
||||||
|
name?: string;
|
||||||
|
description?: string;
|
||||||
|
aliases?: string[];
|
||||||
|
symbols?: SymbolEntry[];
|
||||||
|
}
|
||||||
|
|
||||||
export type JobResponseType = (typeof JobResponseType)[keyof typeof JobResponseType];
|
export type JobResponseType = (typeof JobResponseType)[keyof typeof JobResponseType];
|
||||||
|
|
||||||
export const JobResponseType = {
|
export const JobResponseType = {
|
||||||
@ -160,8 +177,8 @@ export interface PageableObject {
|
|||||||
paged?: boolean;
|
paged?: boolean;
|
||||||
pageNumber?: number;
|
pageNumber?: number;
|
||||||
pageSize?: number;
|
pageSize?: number;
|
||||||
unpaged?: boolean;
|
|
||||||
sort?: SortObject;
|
sort?: SortObject;
|
||||||
|
unpaged?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PageLibrarySymbolResponse {
|
export interface PageLibrarySymbolResponse {
|
||||||
@ -171,10 +188,10 @@ export interface PageLibrarySymbolResponse {
|
|||||||
content?: LibrarySymbolResponse[];
|
content?: LibrarySymbolResponse[];
|
||||||
number?: number;
|
number?: number;
|
||||||
pageable?: PageableObject;
|
pageable?: PageableObject;
|
||||||
numberOfElements?: number;
|
|
||||||
sort?: SortObject;
|
|
||||||
first?: boolean;
|
first?: boolean;
|
||||||
last?: boolean;
|
last?: boolean;
|
||||||
|
sort?: SortObject;
|
||||||
|
numberOfElements?: number;
|
||||||
empty?: boolean;
|
empty?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -357,10 +374,10 @@ export interface PageFunctionSummaryResponse {
|
|||||||
content?: FunctionSummaryResponse[];
|
content?: FunctionSummaryResponse[];
|
||||||
number?: number;
|
number?: number;
|
||||||
pageable?: PageableObject;
|
pageable?: PageableObject;
|
||||||
numberOfElements?: number;
|
|
||||||
sort?: SortObject;
|
|
||||||
first?: boolean;
|
first?: boolean;
|
||||||
last?: boolean;
|
last?: boolean;
|
||||||
|
sort?: SortObject;
|
||||||
|
numberOfElements?: number;
|
||||||
empty?: boolean;
|
empty?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
/**
|
/**
|
||||||
* Generated by orval v8.15.0 🍺
|
* Generated by orval v8.20.0 🍺
|
||||||
* Do not edit manually.
|
* Do not edit manually.
|
||||||
* OpenAPI definition
|
* OpenAPI definition
|
||||||
* OpenAPI spec version: v0
|
* OpenAPI spec version: v0
|
||||||
@ -35,6 +35,21 @@ import { customInstance } from "../../api";
|
|||||||
|
|
||||||
type SecondParameter<T extends (...args: never) => unknown> = Parameters<T>[1];
|
type SecondParameter<T extends (...args: never) => unknown> = Parameters<T>[1];
|
||||||
|
|
||||||
|
const withQueryKey = <T extends object, K>(query: T, queryKey: K): T & { queryKey: K } => {
|
||||||
|
const result = { queryKey } as T & { queryKey: K };
|
||||||
|
for (const key of Object.keys(query)) {
|
||||||
|
// The explicit queryKey always wins, matching the previous
|
||||||
|
// `{ ...query, queryKey }` spread where it was set last.
|
||||||
|
if (key === "queryKey") continue;
|
||||||
|
Object.defineProperty(result, key, {
|
||||||
|
enumerable: true,
|
||||||
|
configurable: true,
|
||||||
|
get: () => (query as Record<string, unknown>)[key],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve all binaries belonging to a project, optionally filtered by filename.
|
* Retrieve all binaries belonging to a project, optionally filtered by filename.
|
||||||
* @summary List binaries of a project
|
* @summary List binaries of a project
|
||||||
@ -153,7 +168,7 @@ export function useGetBinaries<TData = Awaited<ReturnType<typeof getBinaries>>,
|
|||||||
queryKey: DataTag<QueryKey, TData, TError>;
|
queryKey: DataTag<QueryKey, TData, TError>;
|
||||||
};
|
};
|
||||||
|
|
||||||
return { ...query, queryKey: queryOptions.queryKey };
|
return withQueryKey(query, queryOptions.queryKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -370,7 +385,7 @@ export function useGetDependencies<
|
|||||||
queryKey: DataTag<QueryKey, TData, TError>;
|
queryKey: DataTag<QueryKey, TData, TError>;
|
||||||
};
|
};
|
||||||
|
|
||||||
return { ...query, queryKey: queryOptions.queryKey };
|
return withQueryKey(query, queryOptions.queryKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -651,7 +666,7 @@ export function useGetBinary<TData = Awaited<ReturnType<typeof getBinary>>, TErr
|
|||||||
queryKey: DataTag<QueryKey, TData, TError>;
|
queryKey: DataTag<QueryKey, TData, TError>;
|
||||||
};
|
};
|
||||||
|
|
||||||
return { ...query, queryKey: queryOptions.queryKey };
|
return withQueryKey(query, queryOptions.queryKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -852,7 +867,7 @@ export function useDownloadBinary<
|
|||||||
queryKey: DataTag<QueryKey, TData, TError>;
|
queryKey: DataTag<QueryKey, TData, TError>;
|
||||||
};
|
};
|
||||||
|
|
||||||
return { ...query, queryKey: queryOptions.queryKey };
|
return withQueryKey(query, queryOptions.queryKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
/**
|
/**
|
||||||
* Generated by orval v8.15.0 🍺
|
* Generated by orval v8.20.0 🍺
|
||||||
* Do not edit manually.
|
* Do not edit manually.
|
||||||
* OpenAPI definition
|
* OpenAPI definition
|
||||||
* OpenAPI spec version: v0
|
* OpenAPI spec version: v0
|
||||||
@ -26,6 +26,21 @@ import { customInstance } from "../../api";
|
|||||||
|
|
||||||
type SecondParameter<T extends (...args: never) => unknown> = Parameters<T>[1];
|
type SecondParameter<T extends (...args: never) => unknown> = Parameters<T>[1];
|
||||||
|
|
||||||
|
const withQueryKey = <T extends object, K>(query: T, queryKey: K): T & { queryKey: K } => {
|
||||||
|
const result = { queryKey } as T & { queryKey: K };
|
||||||
|
for (const key of Object.keys(query)) {
|
||||||
|
// The explicit queryKey always wins, matching the previous
|
||||||
|
// `{ ...query, queryKey }` spread where it was set last.
|
||||||
|
if (key === "queryKey") continue;
|
||||||
|
Object.defineProperty(result, key, {
|
||||||
|
enumerable: true,
|
||||||
|
configurable: true,
|
||||||
|
get: () => (query as Record<string, unknown>)[key],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @summary List chat sessions for a binary
|
* @summary List chat sessions for a binary
|
||||||
*/
|
*/
|
||||||
@ -136,7 +151,7 @@ export function useListSessions<TData = Awaited<ReturnType<typeof listSessions>>
|
|||||||
queryKey: DataTag<QueryKey, TData, TError>;
|
queryKey: DataTag<QueryKey, TData, TError>;
|
||||||
};
|
};
|
||||||
|
|
||||||
return { ...query, queryKey: queryOptions.queryKey };
|
return withQueryKey(query, queryOptions.queryKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -324,7 +339,7 @@ export function useGetSession<TData = Awaited<ReturnType<typeof getSession>>, TE
|
|||||||
queryKey: DataTag<QueryKey, TData, TError>;
|
queryKey: DataTag<QueryKey, TData, TError>;
|
||||||
};
|
};
|
||||||
|
|
||||||
return { ...query, queryKey: queryOptions.queryKey };
|
return withQueryKey(query, queryOptions.queryKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -510,5 +525,5 @@ export function useGetMessages<TData = Awaited<ReturnType<typeof getMessages>>,
|
|||||||
queryKey: DataTag<QueryKey, TData, TError>;
|
queryKey: DataTag<QueryKey, TData, TError>;
|
||||||
};
|
};
|
||||||
|
|
||||||
return { ...query, queryKey: queryOptions.queryKey };
|
return withQueryKey(query, queryOptions.queryKey);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
/**
|
/**
|
||||||
* Generated by orval v8.15.0 🍺
|
* Generated by orval v8.20.0 🍺
|
||||||
* Do not edit manually.
|
* Do not edit manually.
|
||||||
* OpenAPI definition
|
* OpenAPI definition
|
||||||
* OpenAPI spec version: v0
|
* OpenAPI spec version: v0
|
||||||
@ -23,6 +23,21 @@ import { customInstance } from "../../api";
|
|||||||
|
|
||||||
type SecondParameter<T extends (...args: never) => unknown> = Parameters<T>[1];
|
type SecondParameter<T extends (...args: never) => unknown> = Parameters<T>[1];
|
||||||
|
|
||||||
|
const withQueryKey = <T extends object, K>(query: T, queryKey: K): T & { queryKey: K } => {
|
||||||
|
const result = { queryKey } as T & { queryKey: K };
|
||||||
|
for (const key of Object.keys(query)) {
|
||||||
|
// The explicit queryKey always wins, matching the previous
|
||||||
|
// `{ ...query, queryKey }` spread where it was set last.
|
||||||
|
if (key === "queryKey") continue;
|
||||||
|
Object.defineProperty(result, key, {
|
||||||
|
enumerable: true,
|
||||||
|
configurable: true,
|
||||||
|
get: () => (query as Record<string, unknown>)[key],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve all registered static analysis engines with their availability status.
|
* Retrieve all registered static analysis engines with their availability status.
|
||||||
* @summary List available analysis engines
|
* @summary List available analysis engines
|
||||||
@ -116,5 +131,5 @@ export function useListEngines<TData = Awaited<ReturnType<typeof listEngines>>,
|
|||||||
queryKey: DataTag<QueryKey, TData, TError>;
|
queryKey: DataTag<QueryKey, TData, TError>;
|
||||||
};
|
};
|
||||||
|
|
||||||
return { ...query, queryKey: queryOptions.queryKey };
|
return withQueryKey(query, queryOptions.queryKey);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
/**
|
/**
|
||||||
* Generated by orval v8.15.0 🍺
|
* Generated by orval v8.20.0 🍺
|
||||||
* Do not edit manually.
|
* Do not edit manually.
|
||||||
* OpenAPI definition
|
* OpenAPI definition
|
||||||
* OpenAPI spec version: v0
|
* OpenAPI spec version: v0
|
||||||
@ -23,6 +23,21 @@ import { customInstance } from "../../api";
|
|||||||
|
|
||||||
type SecondParameter<T extends (...args: never) => unknown> = Parameters<T>[1];
|
type SecondParameter<T extends (...args: never) => unknown> = Parameters<T>[1];
|
||||||
|
|
||||||
|
const withQueryKey = <T extends object, K>(query: T, queryKey: K): T & { queryKey: K } => {
|
||||||
|
const result = { queryKey } as T & { queryKey: K };
|
||||||
|
for (const key of Object.keys(query)) {
|
||||||
|
// The explicit queryKey always wins, matching the previous
|
||||||
|
// `{ ...query, queryKey }` spread where it was set last.
|
||||||
|
if (key === "queryKey") continue;
|
||||||
|
Object.defineProperty(result, key, {
|
||||||
|
enumerable: true,
|
||||||
|
configurable: true,
|
||||||
|
get: () => (query as Record<string, unknown>)[key],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve full details of a function including assembly, decompiled code, and xref counts.
|
* Retrieve full details of a function including assembly, decompiled code, and xref counts.
|
||||||
* @summary Get function details
|
* @summary Get function details
|
||||||
@ -130,7 +145,7 @@ export function useGetFunction<TData = Awaited<ReturnType<typeof getFunction>>,
|
|||||||
queryKey: DataTag<QueryKey, TData, TError>;
|
queryKey: DataTag<QueryKey, TData, TError>;
|
||||||
};
|
};
|
||||||
|
|
||||||
return { ...query, queryKey: queryOptions.queryKey };
|
return withQueryKey(query, queryOptions.queryKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -240,7 +255,7 @@ export function useGetCallers<TData = Awaited<ReturnType<typeof getCallers>>, TE
|
|||||||
queryKey: DataTag<QueryKey, TData, TError>;
|
queryKey: DataTag<QueryKey, TData, TError>;
|
||||||
};
|
};
|
||||||
|
|
||||||
return { ...query, queryKey: queryOptions.queryKey };
|
return withQueryKey(query, queryOptions.queryKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -350,5 +365,5 @@ export function useGetCallees<TData = Awaited<ReturnType<typeof getCallees>>, TE
|
|||||||
queryKey: DataTag<QueryKey, TData, TError>;
|
queryKey: DataTag<QueryKey, TData, TError>;
|
||||||
};
|
};
|
||||||
|
|
||||||
return { ...query, queryKey: queryOptions.queryKey };
|
return withQueryKey(query, queryOptions.queryKey);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
/**
|
/**
|
||||||
* Generated by orval v8.15.0 🍺
|
* Generated by orval v8.20.0 🍺
|
||||||
* Do not edit manually.
|
* Do not edit manually.
|
||||||
* OpenAPI definition
|
* OpenAPI definition
|
||||||
* OpenAPI spec version: v0
|
* OpenAPI spec version: v0
|
||||||
@ -26,6 +26,21 @@ import { customInstance } from "../../api";
|
|||||||
|
|
||||||
type SecondParameter<T extends (...args: never) => unknown> = Parameters<T>[1];
|
type SecondParameter<T extends (...args: never) => unknown> = Parameters<T>[1];
|
||||||
|
|
||||||
|
const withQueryKey = <T extends object, K>(query: T, queryKey: K): T & { queryKey: K } => {
|
||||||
|
const result = { queryKey } as T & { queryKey: K };
|
||||||
|
for (const key of Object.keys(query)) {
|
||||||
|
// The explicit queryKey always wins, matching the previous
|
||||||
|
// `{ ...query, queryKey }` spread where it was set last.
|
||||||
|
if (key === "queryKey") continue;
|
||||||
|
Object.defineProperty(result, key, {
|
||||||
|
enumerable: true,
|
||||||
|
configurable: true,
|
||||||
|
get: () => (query as Record<string, unknown>)[key],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Cancel a job that is enqueued, started, or in progress. Returns 409 if the job is already in a final state.
|
* Cancel a job that is enqueued, started, or in progress. Returns 409 if the job is already in a final state.
|
||||||
* @summary Cancel a job
|
* @summary Cancel a job
|
||||||
@ -200,7 +215,7 @@ export function useGetJobs<TData = Awaited<ReturnType<typeof getJobs>>, TError =
|
|||||||
queryKey: DataTag<QueryKey, TData, TError>;
|
queryKey: DataTag<QueryKey, TData, TError>;
|
||||||
};
|
};
|
||||||
|
|
||||||
return { ...query, queryKey: queryOptions.queryKey };
|
return withQueryKey(query, queryOptions.queryKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -307,5 +322,5 @@ export function useGetJob<TData = Awaited<ReturnType<typeof getJob>>, TError = u
|
|||||||
queryKey: DataTag<QueryKey, TData, TError>;
|
queryKey: DataTag<QueryKey, TData, TError>;
|
||||||
};
|
};
|
||||||
|
|
||||||
return { ...query, queryKey: queryOptions.queryKey };
|
return withQueryKey(query, queryOptions.queryKey);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
/**
|
/**
|
||||||
* Generated by orval v8.15.0 🍺
|
* Generated by orval v8.20.0 🍺
|
||||||
* Do not edit manually.
|
* Do not edit manually.
|
||||||
* OpenAPI definition
|
* OpenAPI definition
|
||||||
* OpenAPI spec version: v0
|
* OpenAPI spec version: v0
|
||||||
@ -21,6 +21,7 @@ import type {
|
|||||||
} from "@tanstack/react-query";
|
} from "@tanstack/react-query";
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
|
Library,
|
||||||
LibraryResponse,
|
LibraryResponse,
|
||||||
ListLibrarySymbolsParams,
|
ListLibrarySymbolsParams,
|
||||||
PageLibrarySymbolResponse,
|
PageLibrarySymbolResponse,
|
||||||
@ -34,6 +35,21 @@ import { customInstance } from "../../api";
|
|||||||
|
|
||||||
type SecondParameter<T extends (...args: never) => unknown> = Parameters<T>[1];
|
type SecondParameter<T extends (...args: never) => unknown> = Parameters<T>[1];
|
||||||
|
|
||||||
|
const withQueryKey = <T extends object, K>(query: T, queryKey: K): T & { queryKey: K } => {
|
||||||
|
const result = { queryKey } as T & { queryKey: K };
|
||||||
|
for (const key of Object.keys(query)) {
|
||||||
|
// The explicit queryKey always wins, matching the previous
|
||||||
|
// `{ ...query, queryKey }` spread where it was set last.
|
||||||
|
if (key === "queryKey") continue;
|
||||||
|
Object.defineProperty(result, key, {
|
||||||
|
enumerable: true,
|
||||||
|
configurable: true,
|
||||||
|
get: () => (query as Record<string, unknown>)[key],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parse and store symbols from a library dump file (e.g., TurboDump output).
|
* Parse and store symbols from a library dump file (e.g., TurboDump output).
|
||||||
* @summary Upload a library symbol dump
|
* @summary Upload a library symbol dump
|
||||||
@ -184,6 +200,86 @@ export const useResolveAllSymbols = <TError = unknown, TContext = unknown>(
|
|||||||
): UseMutationResult<Awaited<ReturnType<typeof resolveAllSymbols>>, TError, void, TContext> => {
|
): UseMutationResult<Awaited<ReturnType<typeof resolveAllSymbols>>, TError, void, TContext> => {
|
||||||
return useMutation(getResolveAllSymbolsMutationOptions(options), queryClient);
|
return useMutation(getResolveAllSymbolsMutationOptions(options), queryClient);
|
||||||
};
|
};
|
||||||
|
/**
|
||||||
|
* Create a new library from a previously exported JSON backup. The new library gets its own IDs — existing libraries are not affected.
|
||||||
|
* @summary Import a library from JSON export
|
||||||
|
*/
|
||||||
|
export const importLibrary = (
|
||||||
|
library: Library,
|
||||||
|
options?: SecondParameter<typeof customInstance>,
|
||||||
|
signal?: AbortSignal
|
||||||
|
) => {
|
||||||
|
return customInstance<LibraryResponse>(
|
||||||
|
{
|
||||||
|
url: `/libraries/import`,
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
data: library,
|
||||||
|
signal,
|
||||||
|
},
|
||||||
|
options
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getImportLibraryMutationOptions = <TError = unknown, TContext = unknown>(options?: {
|
||||||
|
mutation?: UseMutationOptions<
|
||||||
|
Awaited<ReturnType<typeof importLibrary>>,
|
||||||
|
TError,
|
||||||
|
{ data: Library },
|
||||||
|
TContext
|
||||||
|
>;
|
||||||
|
request?: SecondParameter<typeof customInstance>;
|
||||||
|
}): UseMutationOptions<
|
||||||
|
Awaited<ReturnType<typeof importLibrary>>,
|
||||||
|
TError,
|
||||||
|
{ data: Library },
|
||||||
|
TContext
|
||||||
|
> => {
|
||||||
|
const mutationKey = ["importLibrary"];
|
||||||
|
const { mutation: mutationOptions, request: requestOptions } = options
|
||||||
|
? options.mutation && "mutationKey" in options.mutation && options.mutation.mutationKey
|
||||||
|
? options
|
||||||
|
: { ...options, mutation: { ...options.mutation, mutationKey } }
|
||||||
|
: { mutation: { mutationKey }, request: undefined };
|
||||||
|
|
||||||
|
const mutationFn: MutationFunction<
|
||||||
|
Awaited<ReturnType<typeof importLibrary>>,
|
||||||
|
{ data: Library }
|
||||||
|
> = (props) => {
|
||||||
|
const { data } = props ?? {};
|
||||||
|
|
||||||
|
return importLibrary(data, requestOptions);
|
||||||
|
};
|
||||||
|
|
||||||
|
return { mutationFn, ...mutationOptions };
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ImportLibraryMutationResult = NonNullable<Awaited<ReturnType<typeof importLibrary>>>;
|
||||||
|
export type ImportLibraryMutationBody = Library;
|
||||||
|
export type ImportLibraryMutationError = unknown;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @summary Import a library from JSON export
|
||||||
|
*/
|
||||||
|
export const useImportLibrary = <TError = unknown, TContext = unknown>(
|
||||||
|
options?: {
|
||||||
|
mutation?: UseMutationOptions<
|
||||||
|
Awaited<ReturnType<typeof importLibrary>>,
|
||||||
|
TError,
|
||||||
|
{ data: Library },
|
||||||
|
TContext
|
||||||
|
>;
|
||||||
|
request?: SecondParameter<typeof customInstance>;
|
||||||
|
},
|
||||||
|
queryClient?: QueryClient
|
||||||
|
): UseMutationResult<
|
||||||
|
Awaited<ReturnType<typeof importLibrary>>,
|
||||||
|
TError,
|
||||||
|
{ data: Library },
|
||||||
|
TContext
|
||||||
|
> => {
|
||||||
|
return useMutation(getImportLibraryMutationOptions(options), queryClient);
|
||||||
|
};
|
||||||
/**
|
/**
|
||||||
* Cross-reference imports from a specific analysis against all loaded library symbol databases.
|
* Cross-reference imports from a specific analysis against all loaded library symbol databases.
|
||||||
* @summary Resolve symbols for an analysis
|
* @summary Resolve symbols for an analysis
|
||||||
@ -367,7 +463,7 @@ export function useListLibraries<
|
|||||||
queryKey: DataTag<QueryKey, TData, TError>;
|
queryKey: DataTag<QueryKey, TData, TError>;
|
||||||
};
|
};
|
||||||
|
|
||||||
return { ...query, queryKey: queryOptions.queryKey };
|
return withQueryKey(query, queryOptions.queryKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -477,7 +573,7 @@ export function useGetLibrary<TData = Awaited<ReturnType<typeof getLibrary>>, TE
|
|||||||
queryKey: DataTag<QueryKey, TData, TError>;
|
queryKey: DataTag<QueryKey, TData, TError>;
|
||||||
};
|
};
|
||||||
|
|
||||||
return { ...query, queryKey: queryOptions.queryKey };
|
return withQueryKey(query, queryOptions.queryKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -691,7 +787,129 @@ export function useListLibrarySymbols<
|
|||||||
queryKey: DataTag<QueryKey, TData, TError>;
|
queryKey: DataTag<QueryKey, TData, TError>;
|
||||||
};
|
};
|
||||||
|
|
||||||
return { ...query, queryKey: queryOptions.queryKey };
|
return withQueryKey(query, queryOptions.queryKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Download the complete library (name, description, aliases, and all symbols) as a JSON file for backup or transfer.
|
||||||
|
* @summary Export a library as JSON
|
||||||
|
*/
|
||||||
|
export const exportLibrary = (
|
||||||
|
libraryId: string,
|
||||||
|
options?: SecondParameter<typeof customInstance>,
|
||||||
|
signal?: AbortSignal
|
||||||
|
) => {
|
||||||
|
return customInstance<Library>(
|
||||||
|
{ url: `/libraries/${encodeURIComponent(String(libraryId))}/export`, method: "GET", signal },
|
||||||
|
options
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getExportLibraryQueryKey = (libraryId: string) => {
|
||||||
|
return [`/libraries/${libraryId}/export`] as const;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getExportLibraryQueryOptions = <
|
||||||
|
TData = Awaited<ReturnType<typeof exportLibrary>>,
|
||||||
|
TError = unknown,
|
||||||
|
>(
|
||||||
|
libraryId: string,
|
||||||
|
options?: {
|
||||||
|
query?: Partial<UseQueryOptions<Awaited<ReturnType<typeof exportLibrary>>, TError, TData>>;
|
||||||
|
request?: SecondParameter<typeof customInstance>;
|
||||||
|
}
|
||||||
|
) => {
|
||||||
|
const { query: queryOptions, request: requestOptions } = options ?? {};
|
||||||
|
|
||||||
|
const queryKey = queryOptions?.queryKey ?? getExportLibraryQueryKey(libraryId);
|
||||||
|
|
||||||
|
const queryFn: QueryFunction<Awaited<ReturnType<typeof exportLibrary>>> = ({ signal }) =>
|
||||||
|
exportLibrary(libraryId, requestOptions, signal);
|
||||||
|
|
||||||
|
return {
|
||||||
|
queryKey,
|
||||||
|
queryFn,
|
||||||
|
enabled: libraryId !== null && libraryId !== undefined,
|
||||||
|
...queryOptions,
|
||||||
|
} as UseQueryOptions<Awaited<ReturnType<typeof exportLibrary>>, TError, TData> & {
|
||||||
|
queryKey: DataTag<QueryKey, TData, TError>;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ExportLibraryQueryResult = NonNullable<Awaited<ReturnType<typeof exportLibrary>>>;
|
||||||
|
export type ExportLibraryQueryError = unknown;
|
||||||
|
|
||||||
|
export function useExportLibrary<
|
||||||
|
TData = Awaited<ReturnType<typeof exportLibrary>>,
|
||||||
|
TError = unknown,
|
||||||
|
>(
|
||||||
|
libraryId: string,
|
||||||
|
options: {
|
||||||
|
query: Partial<UseQueryOptions<Awaited<ReturnType<typeof exportLibrary>>, TError, TData>> &
|
||||||
|
Pick<
|
||||||
|
DefinedInitialDataOptions<
|
||||||
|
Awaited<ReturnType<typeof exportLibrary>>,
|
||||||
|
TError,
|
||||||
|
Awaited<ReturnType<typeof exportLibrary>>
|
||||||
|
>,
|
||||||
|
"initialData"
|
||||||
|
>;
|
||||||
|
request?: SecondParameter<typeof customInstance>;
|
||||||
|
},
|
||||||
|
queryClient?: QueryClient
|
||||||
|
): DefinedUseQueryResult<TData, TError> & { queryKey: DataTag<QueryKey, TData, TError> };
|
||||||
|
export function useExportLibrary<
|
||||||
|
TData = Awaited<ReturnType<typeof exportLibrary>>,
|
||||||
|
TError = unknown,
|
||||||
|
>(
|
||||||
|
libraryId: string,
|
||||||
|
options?: {
|
||||||
|
query?: Partial<UseQueryOptions<Awaited<ReturnType<typeof exportLibrary>>, TError, TData>> &
|
||||||
|
Pick<
|
||||||
|
UndefinedInitialDataOptions<
|
||||||
|
Awaited<ReturnType<typeof exportLibrary>>,
|
||||||
|
TError,
|
||||||
|
Awaited<ReturnType<typeof exportLibrary>>
|
||||||
|
>,
|
||||||
|
"initialData"
|
||||||
|
>;
|
||||||
|
request?: SecondParameter<typeof customInstance>;
|
||||||
|
},
|
||||||
|
queryClient?: QueryClient
|
||||||
|
): UseQueryResult<TData, TError> & { queryKey: DataTag<QueryKey, TData, TError> };
|
||||||
|
export function useExportLibrary<
|
||||||
|
TData = Awaited<ReturnType<typeof exportLibrary>>,
|
||||||
|
TError = unknown,
|
||||||
|
>(
|
||||||
|
libraryId: string,
|
||||||
|
options?: {
|
||||||
|
query?: Partial<UseQueryOptions<Awaited<ReturnType<typeof exportLibrary>>, TError, TData>>;
|
||||||
|
request?: SecondParameter<typeof customInstance>;
|
||||||
|
},
|
||||||
|
queryClient?: QueryClient
|
||||||
|
): UseQueryResult<TData, TError> & { queryKey: DataTag<QueryKey, TData, TError> };
|
||||||
|
/**
|
||||||
|
* @summary Export a library as JSON
|
||||||
|
*/
|
||||||
|
|
||||||
|
export function useExportLibrary<
|
||||||
|
TData = Awaited<ReturnType<typeof exportLibrary>>,
|
||||||
|
TError = unknown,
|
||||||
|
>(
|
||||||
|
libraryId: string,
|
||||||
|
options?: {
|
||||||
|
query?: Partial<UseQueryOptions<Awaited<ReturnType<typeof exportLibrary>>, TError, TData>>;
|
||||||
|
request?: SecondParameter<typeof customInstance>;
|
||||||
|
},
|
||||||
|
queryClient?: QueryClient
|
||||||
|
): UseQueryResult<TData, TError> & { queryKey: DataTag<QueryKey, TData, TError> } {
|
||||||
|
const queryOptions = getExportLibraryQueryOptions(libraryId, options);
|
||||||
|
|
||||||
|
const query = useQuery(queryOptions, queryClient) as UseQueryResult<TData, TError> & {
|
||||||
|
queryKey: DataTag<QueryKey, TData, TError>;
|
||||||
|
};
|
||||||
|
|
||||||
|
return withQueryKey(query, queryOptions.queryKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -787,5 +1005,5 @@ export function useListParsers<TData = Awaited<ReturnType<typeof listParsers>>,
|
|||||||
queryKey: DataTag<QueryKey, TData, TError>;
|
queryKey: DataTag<QueryKey, TData, TError>;
|
||||||
};
|
};
|
||||||
|
|
||||||
return { ...query, queryKey: queryOptions.queryKey };
|
return withQueryKey(query, queryOptions.queryKey);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
/**
|
/**
|
||||||
* Generated by orval v8.15.0 🍺
|
* Generated by orval v8.20.0 🍺
|
||||||
* Do not edit manually.
|
* Do not edit manually.
|
||||||
* OpenAPI definition
|
* OpenAPI definition
|
||||||
* OpenAPI spec version: v0
|
* OpenAPI spec version: v0
|
||||||
@ -26,6 +26,21 @@ import { customInstance } from "../../api";
|
|||||||
|
|
||||||
type SecondParameter<T extends (...args: never) => unknown> = Parameters<T>[1];
|
type SecondParameter<T extends (...args: never) => unknown> = Parameters<T>[1];
|
||||||
|
|
||||||
|
const withQueryKey = <T extends object, K>(query: T, queryKey: K): T & { queryKey: K } => {
|
||||||
|
const result = { queryKey } as T & { queryKey: K };
|
||||||
|
for (const key of Object.keys(query)) {
|
||||||
|
// The explicit queryKey always wins, matching the previous
|
||||||
|
// `{ ...query, queryKey }` spread where it was set last.
|
||||||
|
if (key === "queryKey") continue;
|
||||||
|
Object.defineProperty(result, key, {
|
||||||
|
enumerable: true,
|
||||||
|
configurable: true,
|
||||||
|
get: () => (query as Record<string, unknown>)[key],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reject the suggested mutation without applying it.
|
* Reject the suggested mutation without applying it.
|
||||||
* @summary Reject a mutation suggestion
|
* @summary Reject a mutation suggestion
|
||||||
@ -306,5 +321,5 @@ export function useListMutations<
|
|||||||
queryKey: DataTag<QueryKey, TData, TError>;
|
queryKey: DataTag<QueryKey, TData, TError>;
|
||||||
};
|
};
|
||||||
|
|
||||||
return { ...query, queryKey: queryOptions.queryKey };
|
return withQueryKey(query, queryOptions.queryKey);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
/**
|
/**
|
||||||
* Generated by orval v8.15.0 🍺
|
* Generated by orval v8.20.0 🍺
|
||||||
* Do not edit manually.
|
* Do not edit manually.
|
||||||
* OpenAPI definition
|
* OpenAPI definition
|
||||||
* OpenAPI spec version: v0
|
* OpenAPI spec version: v0
|
||||||
@ -26,6 +26,21 @@ import { customInstance } from "../../api";
|
|||||||
|
|
||||||
type SecondParameter<T extends (...args: never) => unknown> = Parameters<T>[1];
|
type SecondParameter<T extends (...args: never) => unknown> = Parameters<T>[1];
|
||||||
|
|
||||||
|
const withQueryKey = <T extends object, K>(query: T, queryKey: K): T & { queryKey: K } => {
|
||||||
|
const result = { queryKey } as T & { queryKey: K };
|
||||||
|
for (const key of Object.keys(query)) {
|
||||||
|
// The explicit queryKey always wins, matching the previous
|
||||||
|
// `{ ...query, queryKey }` spread where it was set last.
|
||||||
|
if (key === "queryKey") continue;
|
||||||
|
Object.defineProperty(result, key, {
|
||||||
|
enumerable: true,
|
||||||
|
configurable: true,
|
||||||
|
get: () => (query as Record<string, unknown>)[key],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve a single project by ID.
|
* Retrieve a single project by ID.
|
||||||
* @summary Get a project
|
* @summary Get a project
|
||||||
@ -133,7 +148,7 @@ export function useGetProject<TData = Awaited<ReturnType<typeof getProject>>, TE
|
|||||||
queryKey: DataTag<QueryKey, TData, TError>;
|
queryKey: DataTag<QueryKey, TData, TError>;
|
||||||
};
|
};
|
||||||
|
|
||||||
return { ...query, queryKey: queryOptions.queryKey };
|
return withQueryKey(query, queryOptions.queryKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -409,7 +424,7 @@ export function useGetProjects<TData = Awaited<ReturnType<typeof getProjects>>,
|
|||||||
queryKey: DataTag<QueryKey, TData, TError>;
|
queryKey: DataTag<QueryKey, TData, TError>;
|
||||||
};
|
};
|
||||||
|
|
||||||
return { ...query, queryKey: queryOptions.queryKey };
|
return withQueryKey(query, queryOptions.queryKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
/**
|
/**
|
||||||
* Generated by orval v8.15.0 🍺
|
* Generated by orval v8.20.0 🍺
|
||||||
* Do not edit manually.
|
* Do not edit manually.
|
||||||
* OpenAPI definition
|
* OpenAPI definition
|
||||||
* OpenAPI spec version: v0
|
* OpenAPI spec version: v0
|
||||||
@ -26,6 +26,21 @@ import { customInstance } from "../../api";
|
|||||||
|
|
||||||
type SecondParameter<T extends (...args: never) => unknown> = Parameters<T>[1];
|
type SecondParameter<T extends (...args: never) => unknown> = Parameters<T>[1];
|
||||||
|
|
||||||
|
const withQueryKey = <T extends object, K>(query: T, queryKey: K): T & { queryKey: K } => {
|
||||||
|
const result = { queryKey } as T & { queryKey: K };
|
||||||
|
for (const key of Object.keys(query)) {
|
||||||
|
// The explicit queryKey always wins, matching the previous
|
||||||
|
// `{ ...query, queryKey }` spread where it was set last.
|
||||||
|
if (key === "queryKey") continue;
|
||||||
|
Object.defineProperty(result, key, {
|
||||||
|
enumerable: true,
|
||||||
|
configurable: true,
|
||||||
|
get: () => (query as Record<string, unknown>)[key],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve a single workspace by its unique identifier.
|
* Retrieve a single workspace by its unique identifier.
|
||||||
* @summary Get a workspace by ID
|
* @summary Get a workspace by ID
|
||||||
@ -133,7 +148,7 @@ export function useGetWorkspace<TData = Awaited<ReturnType<typeof getWorkspace>>
|
|||||||
queryKey: DataTag<QueryKey, TData, TError>;
|
queryKey: DataTag<QueryKey, TData, TError>;
|
||||||
};
|
};
|
||||||
|
|
||||||
return { ...query, queryKey: queryOptions.queryKey };
|
return withQueryKey(query, queryOptions.queryKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -411,7 +426,7 @@ export function useGetWorkspaces<
|
|||||||
queryKey: DataTag<QueryKey, TData, TError>;
|
queryKey: DataTag<QueryKey, TData, TError>;
|
||||||
};
|
};
|
||||||
|
|
||||||
return { ...query, queryKey: queryOptions.queryKey };
|
return withQueryKey(query, queryOptions.queryKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
166
src/features/libraries/ImportLibraryDialog.tsx
Normal file
166
src/features/libraries/ImportLibraryDialog.tsx
Normal file
@ -0,0 +1,166 @@
|
|||||||
|
import { useState, useRef } from "react";
|
||||||
|
import { Upload, Loader2, BookOpen, Hash, Tag } from "lucide-react";
|
||||||
|
import {
|
||||||
|
Dialog,
|
||||||
|
DialogContent,
|
||||||
|
DialogDescription,
|
||||||
|
DialogFooter,
|
||||||
|
DialogHeader,
|
||||||
|
DialogTitle,
|
||||||
|
} from "@/components/ui/dialog.tsx";
|
||||||
|
import { Button } from "@/components/ui/button.tsx";
|
||||||
|
import { Badge } from "@/components/ui/badge.tsx";
|
||||||
|
import type { Library } from "@/api/generated/api.schemas.ts";
|
||||||
|
|
||||||
|
const MAX_FILE_SIZE = 50 * 1024 * 1024;
|
||||||
|
|
||||||
|
interface ImportLibraryDialogProps {
|
||||||
|
open: boolean;
|
||||||
|
onOpenChange: (open: boolean) => void;
|
||||||
|
isImporting: boolean;
|
||||||
|
onImport: (data: Library) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ImportLibraryDialog = ({
|
||||||
|
open,
|
||||||
|
onOpenChange,
|
||||||
|
isImporting,
|
||||||
|
onImport,
|
||||||
|
}: ImportLibraryDialogProps) => {
|
||||||
|
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||||
|
const [parsedData, setParsedData] = useState<Library | null>(null);
|
||||||
|
const [parseError, setParseError] = useState<string | null>(null);
|
||||||
|
|
||||||
|
const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
const file = e.target.files?.[0] ?? null;
|
||||||
|
setParseError(null);
|
||||||
|
setParsedData(null);
|
||||||
|
|
||||||
|
if (!file) return;
|
||||||
|
|
||||||
|
if (file.size > MAX_FILE_SIZE) {
|
||||||
|
setParseError("File must be under 50MB");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!file.name.endsWith(".json")) {
|
||||||
|
setParseError("Only .json files are accepted");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.onload = () => {
|
||||||
|
try {
|
||||||
|
const json = JSON.parse(reader.result as string);
|
||||||
|
if (!json.name || typeof json.name !== "string" || json.name.trim() === "") {
|
||||||
|
setParseError('Invalid format: "name" field is required');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setParsedData(json);
|
||||||
|
} catch {
|
||||||
|
setParseError("Invalid JSON file");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
reader.onerror = () => {
|
||||||
|
setParseError("Failed to read file");
|
||||||
|
};
|
||||||
|
reader.readAsText(file);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleImport = () => {
|
||||||
|
if (!parsedData) return;
|
||||||
|
onImport(parsedData);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleClose = () => {
|
||||||
|
if (!isImporting) {
|
||||||
|
setParsedData(null);
|
||||||
|
setParseError(null);
|
||||||
|
if (fileInputRef.current) {
|
||||||
|
fileInputRef.current.value = "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
onOpenChange(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const symbolCount = parsedData?.symbols?.length ?? 0;
|
||||||
|
const aliasCount = parsedData?.aliases?.length ?? 0;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog open={open} onOpenChange={handleClose}>
|
||||||
|
<DialogContent className="sm:max-w-lg">
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle>Import Library</DialogTitle>
|
||||||
|
<DialogDescription>
|
||||||
|
Restore a library from a previously exported JSON backup.
|
||||||
|
</DialogDescription>
|
||||||
|
</DialogHeader>
|
||||||
|
|
||||||
|
<div className="space-y-5">
|
||||||
|
<div className="space-y-2">
|
||||||
|
<label htmlFor="import-file" className="text-sm font-medium">
|
||||||
|
JSON backup file *
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
ref={fileInputRef}
|
||||||
|
id="import-file"
|
||||||
|
type="file"
|
||||||
|
accept=".json"
|
||||||
|
onChange={handleFileChange}
|
||||||
|
disabled={isImporting}
|
||||||
|
className="file:bg-primary file:text-primary-foreground hover:file:bg-primary/90 block w-full text-sm file:mr-3 file:cursor-pointer file:rounded-md file:border-0 file:px-3 file:py-1.5 file:text-sm file:font-medium"
|
||||||
|
/>
|
||||||
|
{parseError && <p className="text-destructive text-xs">{parseError}</p>}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{parsedData && (
|
||||||
|
<div className="bg-muted/50 space-y-3 rounded-md p-3">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<BookOpen className="text-muted-foreground h-4 w-4" />
|
||||||
|
<span className="truncate font-medium">{parsedData.name}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{parsedData.description && (
|
||||||
|
<p className="text-muted-foreground line-clamp-2 text-xs">
|
||||||
|
{parsedData.description}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<Badge variant="secondary" className="text-xs">
|
||||||
|
<Hash className="mr-1 h-3 w-3" />
|
||||||
|
{symbolCount} symbols
|
||||||
|
</Badge>
|
||||||
|
{aliasCount > 0 && (
|
||||||
|
<Badge variant="outline" className="text-xs">
|
||||||
|
<Tag className="mr-1 h-3 w-3" />
|
||||||
|
{aliasCount} aliases
|
||||||
|
</Badge>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<DialogFooter>
|
||||||
|
<Button variant="outline" onClick={handleClose} disabled={isImporting}>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
<Button onClick={handleImport} disabled={isImporting || !parsedData}>
|
||||||
|
{isImporting ? (
|
||||||
|
<>
|
||||||
|
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
||||||
|
Importing...
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<Upload className="mr-2 h-4 w-4" />
|
||||||
|
Import
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
</DialogFooter>
|
||||||
|
</div>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
};
|
||||||
@ -1,4 +1,4 @@
|
|||||||
import { Eye, Trash2, BookOpen } from "lucide-react";
|
import { Eye, Trash2, BookOpen, Download } from "lucide-react";
|
||||||
import { Card, CardContent, CardFooter } from "@/components/ui/card";
|
import { Card, CardContent, CardFooter } from "@/components/ui/card";
|
||||||
import { Badge } from "@/components/ui/badge";
|
import { Badge } from "@/components/ui/badge";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
@ -9,9 +9,10 @@ interface LibraryCardProps {
|
|||||||
library: LibraryResponse;
|
library: LibraryResponse;
|
||||||
onDelete: (library: LibraryResponse) => void;
|
onDelete: (library: LibraryResponse) => void;
|
||||||
onViewSymbols: (library: LibraryResponse) => void;
|
onViewSymbols: (library: LibraryResponse) => void;
|
||||||
|
onExport: (library: LibraryResponse) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const LibraryCard = ({ library, onDelete, onViewSymbols }: LibraryCardProps) => {
|
export const LibraryCard = ({ library, onDelete, onViewSymbols, onExport }: LibraryCardProps) => {
|
||||||
return (
|
return (
|
||||||
<Card className="flex flex-col justify-between gap-2 p-4">
|
<Card className="flex flex-col justify-between gap-2 p-4">
|
||||||
<CardContent className="space-y-2 p-0">
|
<CardContent className="space-y-2 p-0">
|
||||||
@ -45,6 +46,9 @@ export const LibraryCard = ({ library, onDelete, onViewSymbols }: LibraryCardPro
|
|||||||
<Button variant="ghost" size="sm" onClick={() => onViewSymbols(library)} className="h-8">
|
<Button variant="ghost" size="sm" onClick={() => onViewSymbols(library)} className="h-8">
|
||||||
<Eye className="h-3.5 w-3.5" />
|
<Eye className="h-3.5 w-3.5" />
|
||||||
</Button>
|
</Button>
|
||||||
|
<Button variant="ghost" size="sm" onClick={() => onExport(library)} className="h-8">
|
||||||
|
<Download className="h-3.5 w-3.5" />
|
||||||
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="sm"
|
size="sm"
|
||||||
|
|||||||
@ -1,17 +1,22 @@
|
|||||||
import { Plus, BookOpen, Wand2 } from "lucide-react";
|
import { useState } from "react";
|
||||||
|
import { Plus, BookOpen, Wand2, FileUp } from "lucide-react";
|
||||||
import { Button } from "@/components/ui/button.tsx";
|
import { Button } from "@/components/ui/button.tsx";
|
||||||
import { EmptyState } from "@/components/EmptyState.tsx";
|
import { EmptyState } from "@/components/EmptyState.tsx";
|
||||||
import { ConfirmDialog } from "@/components/ConfirmDialog.tsx";
|
import { ConfirmDialog } from "@/components/ConfirmDialog.tsx";
|
||||||
import { useLibrariesPage } from "@/features/libraries/hooks/useLibrariesPage.ts";
|
import { useLibrariesPage } from "@/features/libraries/hooks/useLibrariesPage.ts";
|
||||||
import { LibraryCard } from "@/features/libraries/LibraryCard.tsx";
|
import { LibraryCard } from "@/features/libraries/LibraryCard.tsx";
|
||||||
import { UploadLibraryDialog } from "@/features/libraries/UploadLibraryDialog.tsx";
|
import { UploadLibraryDialog } from "@/features/libraries/UploadLibraryDialog.tsx";
|
||||||
|
import { ImportLibraryDialog } from "@/features/libraries/ImportLibraryDialog.tsx";
|
||||||
import { LibrarySymbolsDialog } from "@/features/libraries/LibrarySymbolsDialog.tsx";
|
import { LibrarySymbolsDialog } from "@/features/libraries/LibrarySymbolsDialog.tsx";
|
||||||
import {
|
import {
|
||||||
useUploadLibrary,
|
useUploadLibrary,
|
||||||
|
useImportLibrary,
|
||||||
getListLibrariesQueryKey,
|
getListLibrariesQueryKey,
|
||||||
} from "@/api/generated/library-symbols/library-symbols.ts";
|
} from "@/api/generated/library-symbols/library-symbols.ts";
|
||||||
|
import { Api } from "@/api/api.ts";
|
||||||
import { useQueryClient } from "@tanstack/react-query";
|
import { useQueryClient } from "@tanstack/react-query";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
|
import type { LibraryResponse, Library } from "@/api/generated/api.schemas.ts";
|
||||||
|
|
||||||
const Libraries = () => {
|
const Libraries = () => {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
@ -31,6 +36,45 @@ const Libraries = () => {
|
|||||||
handleViewSymbols,
|
handleViewSymbols,
|
||||||
} = useLibrariesPage();
|
} = useLibrariesPage();
|
||||||
|
|
||||||
|
const [importDialogOpen, setImportDialogOpen] = useState(false);
|
||||||
|
|
||||||
|
const handleExport = async (library: LibraryResponse) => {
|
||||||
|
if (!library.id) return;
|
||||||
|
try {
|
||||||
|
const response = await Api.get(`/libraries/${encodeURIComponent(library.id)}/export`, {
|
||||||
|
responseType: "blob",
|
||||||
|
});
|
||||||
|
const url = window.URL.createObjectURL(new Blob([response.data]));
|
||||||
|
const link = document.createElement("a");
|
||||||
|
link.href = url;
|
||||||
|
const filename = (library.name ?? "library").replace(/[^a-zA-Z0-9._-]/g, "_");
|
||||||
|
link.setAttribute("download", `${filename}.json`);
|
||||||
|
document.body.appendChild(link);
|
||||||
|
link.click();
|
||||||
|
link.remove();
|
||||||
|
window.URL.revokeObjectURL(url);
|
||||||
|
} catch {
|
||||||
|
toast.error("Failed to export library");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const importMutation = useImportLibrary({
|
||||||
|
mutation: {
|
||||||
|
onSuccess: (data) => {
|
||||||
|
queryClient.invalidateQueries({ queryKey: getListLibrariesQueryKey() });
|
||||||
|
toast.success(`${data.name} imported — ${data.symbolCount} symbols, ${data.aliasCount} aliases`);
|
||||||
|
setImportDialogOpen(false);
|
||||||
|
},
|
||||||
|
onError: () => {
|
||||||
|
toast.error("Failed to import library. Make sure the file is a valid export.");
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleImport = (data: Library) => {
|
||||||
|
importMutation.mutate({ data });
|
||||||
|
};
|
||||||
|
|
||||||
const uploadMutation = useUploadLibrary({
|
const uploadMutation = useUploadLibrary({
|
||||||
mutation: {
|
mutation: {
|
||||||
onSuccess: (data) => {
|
onSuccess: (data) => {
|
||||||
@ -69,6 +113,14 @@ const Libraries = () => {
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => setImportDialogOpen(true)}
|
||||||
|
>
|
||||||
|
<FileUp className="mr-1.5 h-4 w-4" />
|
||||||
|
Import Library
|
||||||
|
</Button>
|
||||||
{libraries.length > 0 && (
|
{libraries.length > 0 && (
|
||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
@ -107,6 +159,7 @@ const Libraries = () => {
|
|||||||
library={lib}
|
library={lib}
|
||||||
onDelete={setDeleteTarget}
|
onDelete={setDeleteTarget}
|
||||||
onViewSymbols={handleViewSymbols}
|
onViewSymbols={handleViewSymbols}
|
||||||
|
onExport={handleExport}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
@ -119,6 +172,13 @@ const Libraries = () => {
|
|||||||
onUpload={handleUpload}
|
onUpload={handleUpload}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<ImportLibraryDialog
|
||||||
|
open={importDialogOpen}
|
||||||
|
onOpenChange={setImportDialogOpen}
|
||||||
|
isImporting={importMutation.isPending}
|
||||||
|
onImport={handleImport}
|
||||||
|
/>
|
||||||
|
|
||||||
<ConfirmDialog
|
<ConfirmDialog
|
||||||
open={!!deleteTarget}
|
open={!!deleteTarget}
|
||||||
onOpenChange={(open) => {
|
onOpenChange={(open) => {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user