265 lines
6.2 KiB
TypeScript
265 lines
6.2 KiB
TypeScript
import { computed, ref } from 'vue';
|
|
|
|
import { useNotification } from '~/composables/useNotification';
|
|
import type { WSEP } from '~/types/sockets';
|
|
import { useYtpConfig } from '~/composables/useYtpConfig';
|
|
import { parse_api_error, parse_list_response, request } from '~/utils';
|
|
import type { Pagination } from '~/types/responses';
|
|
import type { StoreItem } from '~/types/store';
|
|
|
|
type HistoryLoadOptions = {
|
|
order?: 'ASC' | 'DESC';
|
|
status?: string;
|
|
perPage?: number;
|
|
};
|
|
|
|
const items = ref<StoreItem[]>([]);
|
|
const pagination = ref<Pagination>({
|
|
page: 1,
|
|
per_page: 50,
|
|
total: 0,
|
|
total_pages: 0,
|
|
has_next: false,
|
|
has_prev: false,
|
|
});
|
|
const isLoading = ref<boolean>(false);
|
|
const isLoaded = ref<boolean>(false);
|
|
const lastError = ref<string | null>(null);
|
|
const throwInstead = ref(false);
|
|
|
|
const readJson = async (response: Response): Promise<unknown> => {
|
|
try {
|
|
const clone = response.clone();
|
|
return await clone.json();
|
|
} catch {
|
|
return null;
|
|
}
|
|
};
|
|
|
|
const ensureSuccess = async (response: Response): Promise<void> => {
|
|
if (response.ok) {
|
|
return;
|
|
}
|
|
|
|
const payload = await readJson(response);
|
|
const message = await parse_api_error(payload);
|
|
throw new Error(message);
|
|
};
|
|
|
|
const handleError = (error: unknown): void => {
|
|
const message = error instanceof Error ? error.message : 'Unexpected error occurred.';
|
|
lastError.value = message;
|
|
useNotification().error(message);
|
|
};
|
|
|
|
const pageSize = computed<number>(() => {
|
|
const config = useYtpConfig();
|
|
return Number(config.app.default_pagination || 50);
|
|
});
|
|
|
|
const buildQuery = (
|
|
page: number,
|
|
perPage: number,
|
|
order: 'ASC' | 'DESC' = 'DESC',
|
|
status?: string,
|
|
): string => {
|
|
const params = new URLSearchParams({
|
|
type: 'done',
|
|
page: String(page),
|
|
per_page: String(perPage),
|
|
order,
|
|
});
|
|
|
|
if (status) {
|
|
params.set('status', status);
|
|
}
|
|
|
|
return params.toString();
|
|
};
|
|
|
|
const load = async (page: number = 1, options: HistoryLoadOptions = {}): Promise<void> => {
|
|
const { order = 'DESC', status, perPage = pageSize.value } = options;
|
|
|
|
isLoading.value = true;
|
|
|
|
try {
|
|
const response = await request(`/api/history?${buildQuery(page, perPage, order, status)}`);
|
|
await ensureSuccess(response);
|
|
|
|
const data = await response.json();
|
|
const { items: loadedItems, pagination: paginationData } =
|
|
await parse_list_response<StoreItem>(data);
|
|
|
|
items.value = loadedItems;
|
|
pagination.value = paginationData;
|
|
isLoaded.value = true;
|
|
lastError.value = null;
|
|
} catch (error) {
|
|
handleError(error);
|
|
if (throwInstead.value) {
|
|
throw error;
|
|
}
|
|
} finally {
|
|
isLoading.value = false;
|
|
}
|
|
};
|
|
|
|
const reload = async (options: HistoryLoadOptions = {}): Promise<void> => {
|
|
const targetPage = isLoaded.value ? pagination.value.page : 1;
|
|
await load(targetPage, options);
|
|
};
|
|
|
|
const remove = async (
|
|
options: {
|
|
ids?: string[];
|
|
status?: string;
|
|
removeFile?: boolean;
|
|
} = {},
|
|
): Promise<number> => {
|
|
const { ids, status, removeFile = true } = options;
|
|
|
|
if (!ids && !status) {
|
|
throw new Error('Either ids or status filter must be provided');
|
|
}
|
|
|
|
try {
|
|
const response = await request('/api/history', {
|
|
method: 'DELETE',
|
|
body: JSON.stringify({
|
|
type: 'done',
|
|
ids,
|
|
status,
|
|
remove_file: removeFile,
|
|
}),
|
|
});
|
|
|
|
await ensureSuccess(response);
|
|
|
|
const result = (await response.json()) as {
|
|
deleted?: number;
|
|
error?: string;
|
|
message?: string;
|
|
};
|
|
|
|
if (result.error || result.message) {
|
|
throw new Error(result.error || result.message);
|
|
}
|
|
|
|
return Number(result.deleted ?? 0);
|
|
} catch (error) {
|
|
handleError(error);
|
|
if (throwInstead.value) {
|
|
throw error;
|
|
}
|
|
return 0;
|
|
}
|
|
};
|
|
|
|
const rename = async (item: StoreItem, newName: string): Promise<boolean> => {
|
|
const trimmedName = newName.trim();
|
|
if (!trimmedName || trimmedName === item.filename?.split('/').pop()) {
|
|
return false;
|
|
}
|
|
|
|
try {
|
|
const response = await request(`/api/history/${item._id}/rename`, {
|
|
method: 'POST',
|
|
body: JSON.stringify({ new_name: trimmedName }),
|
|
});
|
|
|
|
await ensureSuccess(response);
|
|
|
|
const updated = (await response.json()) as StoreItem;
|
|
const index = items.value.findIndex((entry) => entry._id === updated._id);
|
|
|
|
if (index !== -1) {
|
|
items.value = [...items.value.slice(0, index), updated, ...items.value.slice(index + 1)];
|
|
}
|
|
|
|
lastError.value = null;
|
|
return true;
|
|
} catch (error) {
|
|
handleError(error);
|
|
if (throwInstead.value) {
|
|
throw error;
|
|
}
|
|
return false;
|
|
}
|
|
};
|
|
|
|
const reset = (): void => {
|
|
items.value = [];
|
|
pagination.value = {
|
|
page: 1,
|
|
per_page: pageSize.value,
|
|
total: 0,
|
|
total_pages: 0,
|
|
has_next: false,
|
|
has_prev: false,
|
|
};
|
|
isLoaded.value = false;
|
|
lastError.value = null;
|
|
};
|
|
|
|
const update = (id: string, data: StoreItem): void => {
|
|
const index = items.value.findIndex((item) => item._id === id);
|
|
if (index === -1) return;
|
|
items.value = [...items.value.slice(0, index), data, ...items.value.slice(index + 1)];
|
|
};
|
|
|
|
const upsert = (item: StoreItem): void => {
|
|
const existingIndex = items.value.findIndex((existing) => existing._id === item._id);
|
|
|
|
if (existingIndex !== -1) {
|
|
items.value = [
|
|
item,
|
|
...items.value.slice(0, existingIndex),
|
|
...items.value.slice(existingIndex + 1),
|
|
];
|
|
return;
|
|
}
|
|
|
|
items.value = [item, ...items.value];
|
|
pagination.value.total++;
|
|
|
|
if (items.value.length > pagination.value.per_page) {
|
|
items.value = items.value.slice(0, pagination.value.per_page);
|
|
}
|
|
|
|
pagination.value.total_pages = Math.max(
|
|
1,
|
|
Math.ceil(pagination.value.total / pagination.value.per_page),
|
|
);
|
|
pagination.value.has_next = pagination.value.page < pagination.value.total_pages;
|
|
};
|
|
|
|
const moveHandler = (
|
|
shouldHandle: () => boolean = () => isLoaded.value,
|
|
): ((payload: WSEP['item_moved']) => void) => {
|
|
return (payload: WSEP['item_moved']): void => {
|
|
if ('history' !== payload.data.to || !shouldHandle()) {
|
|
return;
|
|
}
|
|
|
|
upsert(payload.data.item);
|
|
};
|
|
};
|
|
|
|
export const useHistoryState = () => {
|
|
return {
|
|
items,
|
|
pagination,
|
|
isLoading,
|
|
isLoaded,
|
|
lastError,
|
|
pageSize,
|
|
load,
|
|
reload,
|
|
remove,
|
|
rename,
|
|
reset,
|
|
update,
|
|
upsert,
|
|
moveHandler,
|
|
};
|
|
};
|