Release v0.1.3

This commit is contained in:
2026-06-26 01:39:18 +02:00
parent 189e950589
commit a7895ad394
9 changed files with 356 additions and 91 deletions

View File

@@ -1,4 +1,4 @@
import { apiFetch, apiUrl, authHeaders, csrfToken, type ApiSettings } from "@govoplan/core-webui";
import { ApiError, apiFetch, apiUrl, authHeaders, csrfToken, type ApiSettings } from "@govoplan/core-webui";
export type FileSpace = {
id: string;
@@ -40,6 +40,7 @@ export type ManagedFile = {
export type FileListResponse = { files: ManagedFile[] };
export type FileSpacesResponse = { spaces: FileSpace[] };
export type FileUploadResponse = { files: ManagedFile[] };
export type FileUploadProgress = { loaded: number; total?: number; percentage: number | null };
export type FileFolder = {
id: string;
tenant_id: string;
@@ -102,7 +103,16 @@ export function listFiles(settings: ApiSettings, params: { owner_type?: string;
export async function uploadFiles(
settings: ApiSettings,
files: File[],
options: { owner_type: "user" | "group"; owner_id: string; path?: string; campaign_id?: string; unpack_zip?: boolean; conflict_strategy?: ConflictStrategy; conflict_resolutions?: ConflictResolution[] }
options: {
owner_type: "user" | "group";
owner_id: string;
path?: string;
campaign_id?: string;
unpack_zip?: boolean;
conflict_strategy?: ConflictStrategy;
conflict_resolutions?: ConflictResolution[];
onProgress?: (progress: FileUploadProgress) => void;
}
): Promise<FileUploadResponse> {
const form = new FormData();
files.forEach((file) => form.append("files", file));
@@ -113,9 +123,54 @@ export async function uploadFiles(
if (options.unpack_zip) form.append("unpack_zip", "true");
if (options.conflict_strategy) form.append("conflict_strategy", options.conflict_strategy);
if (options.conflict_resolutions?.length) form.append("conflict_resolutions_json", JSON.stringify(options.conflict_resolutions));
if (options.onProgress) return uploadFilesWithProgress(settings, form, options.onProgress);
return apiFetch<FileUploadResponse>(settings, "/api/v1/files/upload", { method: "POST", body: form });
}
function uploadFilesWithProgress(
settings: ApiSettings,
form: FormData,
onProgress: (progress: FileUploadProgress) => void
): Promise<FileUploadResponse> {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open("POST", apiUrl(settings, "/api/v1/files/upload"));
xhr.withCredentials = true;
for (const [key, value] of authHeaders(settings)) xhr.setRequestHeader(key, value);
const csrf = csrfToken();
if (csrf) xhr.setRequestHeader("X-CSRF-Token", csrf);
xhr.upload.onprogress = (event) => {
const total = event.lengthComputable ? event.total : undefined;
onProgress({
loaded: event.loaded,
total,
percentage: total && total > 0 ? Math.round((event.loaded / total) * 100) : null
});
};
xhr.onerror = () => reject(new Error("Upload failed because the network request could not be completed."));
xhr.onabort = () => reject(new Error("Upload was cancelled."));
xhr.onload = () => {
const responseText = xhr.responseText || "";
if (xhr.status < 200 || xhr.status >= 300) {
reject(new ApiError(xhr.status, xhr.statusText, responseText));
return;
}
onProgress({ loaded: 1, total: 1, percentage: 100 });
if (xhr.status === 204 || !responseText) {
resolve({ files: [] });
return;
}
const contentType = xhr.getResponseHeader("content-type") || "";
resolve(contentType.includes("application/json") ? JSON.parse(responseText) as FileUploadResponse : { files: [] });
};
onProgress({ loaded: 0, total: undefined, percentage: 0 });
xhr.send(form);
});
}
export function deleteFile(settings: ApiSettings, fileId: string): Promise<BulkDeleteResponse> {
return apiFetch<BulkDeleteResponse>(settings, `/api/v1/files/${fileId}`, { method: "DELETE" });
}
@@ -124,13 +179,22 @@ export function bulkDeleteFiles(settings: ApiSettings, fileIds: string[]): Promi
return apiFetch<BulkDeleteResponse>(settings, "/api/v1/files/bulk-delete", { method: "POST", body: JSON.stringify({ file_ids: fileIds }) });
}
export function shareFileWithCampaign(settings: ApiSettings, fileId: string, campaignId: string): Promise<FileShare> {
return apiFetch<FileShare>(settings, `/api/v1/files/${fileId}/shares`, {
export type FileBulkShareResponse = { shares: FileShare[]; shared_count: number };
export function shareFilesWithCampaign(settings: ApiSettings, fileIds: string[], campaignId: string): Promise<FileBulkShareResponse> {
return apiFetch<FileBulkShareResponse>(settings, "/api/v1/files/bulk-shares", {
method: "POST",
body: JSON.stringify({ target_type: "campaign", target_id: campaignId, permission: "read" })
body: JSON.stringify({ file_ids: fileIds, target_type: "campaign", target_id: campaignId, permission: "read" })
});
}
export async function shareFileWithCampaign(settings: ApiSettings, fileId: string, campaignId: string): Promise<FileShare> {
const response = await shareFilesWithCampaign(settings, [fileId], campaignId);
const share = response.shares[0];
if (!share) throw new Error("File share was not created.");
return share;
}
export function bulkRenameFiles(
settings: ApiSettings,
payload: { file_ids: string[]; folder_paths?: string[]; owner_type?: "user" | "group"; owner_id?: string; mode: "direct" | "prefix" | "suffix" | "replace"; new_name?: string; find?: string; replacement?: string; prefix?: string; suffix?: string; recursive?: boolean; dry_run?: boolean }