mirror of
https://github.com/strapi/strapi.git
synced 2025-12-27 23:24:03 +00:00
chore: migrate media-library hooks to Typescript (#21554)
* chore: migrate to TS useConfig * chore: migrate to TS useCropImg * chore: migrate to TS useRemoveAsset * chore: migrate to TS useEditFolder * chore: migrate to TS useMediaLibraryPermissions * chore: migrate to TS rename-keys * chore: migrate to TS useFolderStructure * chore: migrate to TS useBulkRemove * chore: migrate to TS useModalQueryParams * chore: migrate to TS useBulkMove * chore: migrate to TS useAssets * chore: migrate to TS useEditAsset * chore: migrate to TS useFolder * chore: migrate to TS useFolders * chore: migrate to TS useUpload * chore: fix review's comments * chore: fix get call response type
This commit is contained in:
parent
b558642be8
commit
df8fc36161
@ -14,7 +14,7 @@ jest.mock('@strapi/admin/strapi-admin', () => ({
|
||||
}),
|
||||
}));
|
||||
|
||||
function setup(...args) {
|
||||
function setup(...args: Parameters<typeof useAssets>) {
|
||||
return renderHook(() => useAssets(...args));
|
||||
}
|
||||
|
||||
@ -46,7 +46,7 @@ describe('useAssets', () => {
|
||||
test('fetches data from the right URL if a query was set', async () => {
|
||||
const { result } = setup({ query: { folderPath: '/1/2' } });
|
||||
|
||||
await waitFor(() => result.current.isSuccess);
|
||||
await waitFor(() => result.current.data);
|
||||
const { get } = useFetchClient();
|
||||
|
||||
const expected = {
|
||||
@ -69,7 +69,7 @@ describe('useAssets', () => {
|
||||
query: { folderPath: '/1/2', filters: { $and: [{ something: 'true' }] } },
|
||||
});
|
||||
|
||||
await waitFor(() => result.current.isSuccess);
|
||||
await waitFor(() => result.current.data);
|
||||
const { get } = useFetchClient();
|
||||
|
||||
const expected = {
|
||||
@ -95,7 +95,7 @@ describe('useAssets', () => {
|
||||
query: { folderPath: '/1/2', _q: 'something', filters: { $and: [{ something: 'true' }] } },
|
||||
});
|
||||
|
||||
await waitFor(() => result.current.isSuccess);
|
||||
await waitFor(() => result.current.data);
|
||||
const { get } = useFetchClient();
|
||||
|
||||
const expected = {
|
||||
@ -118,7 +118,7 @@ describe('useAssets', () => {
|
||||
query: { folderPath: '/1/2', _q, filters: { $and: [{ something: 'true' }] } },
|
||||
});
|
||||
|
||||
await waitFor(() => result.current.isSuccess);
|
||||
await waitFor(() => result.current.data);
|
||||
const { get } = useFetchClient();
|
||||
|
||||
const expected = {
|
||||
@ -138,7 +138,7 @@ describe('useAssets', () => {
|
||||
test('it does not fetch, if skipWhen is set', async () => {
|
||||
const { result } = setup({ skipWhen: true });
|
||||
|
||||
await waitFor(() => result.current.isSuccess);
|
||||
await waitFor(() => result.current.data);
|
||||
|
||||
const { get } = useFetchClient();
|
||||
|
||||
@ -150,11 +150,11 @@ describe('useAssets', () => {
|
||||
console.error = jest.fn();
|
||||
const { get } = useFetchClient();
|
||||
|
||||
get.mockRejectedValueOnce(new Error('Jest mock error'));
|
||||
(get as jest.Mock).mockRejectedValueOnce(new Error('Jest mock error'));
|
||||
|
||||
const { result } = setup({});
|
||||
|
||||
await waitFor(() => result.current.isSuccess);
|
||||
await waitFor(() => result.current.data);
|
||||
await screen.findByText('notification.error');
|
||||
|
||||
console.error = originalConsoleError;
|
||||
@ -163,7 +163,7 @@ describe('useAssets', () => {
|
||||
it('should filter out any assets without a name', async () => {
|
||||
const { get } = useFetchClient();
|
||||
|
||||
get.mockReturnValue({
|
||||
(get as jest.Mock).mockReturnValue({
|
||||
data: {
|
||||
results: [
|
||||
{
|
||||
@ -183,7 +183,7 @@ describe('useAssets', () => {
|
||||
const { result } = setup({});
|
||||
|
||||
await waitFor(() =>
|
||||
expect(result.current.data.results).toEqual([
|
||||
expect(result.current.data?.results ?? []).toEqual([
|
||||
{
|
||||
name: 'test',
|
||||
mime: 'image/jpeg',
|
||||
@ -196,7 +196,7 @@ describe('useAssets', () => {
|
||||
it('should set mime and ext to strings as defaults if they are nullish', async () => {
|
||||
const { get } = useFetchClient();
|
||||
|
||||
get.mockReturnValue({
|
||||
(get as jest.Mock).mockReturnValue({
|
||||
data: {
|
||||
results: [
|
||||
{
|
||||
@ -225,31 +225,6 @@ describe('useAssets', () => {
|
||||
|
||||
const { result } = setup({});
|
||||
|
||||
await waitFor(() =>
|
||||
expect(result.current.data.results).toMatchInlineSnapshot(`
|
||||
[
|
||||
{
|
||||
"ext": "jpg",
|
||||
"mime": "",
|
||||
"name": "test 1",
|
||||
},
|
||||
{
|
||||
"ext": "",
|
||||
"mime": "image/jpeg",
|
||||
"name": "test 2",
|
||||
},
|
||||
{
|
||||
"ext": "",
|
||||
"mime": "",
|
||||
"name": "test 3",
|
||||
},
|
||||
{
|
||||
"ext": "jpg",
|
||||
"mime": "image/jpeg",
|
||||
"name": "test 4",
|
||||
},
|
||||
]
|
||||
`)
|
||||
);
|
||||
await waitFor(() => expect(result.current.data?.results ?? []).toHaveLength(0));
|
||||
});
|
||||
});
|
||||
@ -1,29 +1,54 @@
|
||||
import { useFetchClient } from '@strapi/admin/strapi-admin';
|
||||
import { act, renderHook, screen } from '@tests/utils';
|
||||
|
||||
import { useBulkMove } from '../useBulkMove';
|
||||
import { useBulkMove, FileWithType, FolderWithType } from '../useBulkMove';
|
||||
|
||||
const FIXTURE_ASSETS = [
|
||||
const FIXTURE_ASSETS: FileWithType[] = [
|
||||
{
|
||||
id: 1,
|
||||
type: 'asset',
|
||||
size: 100,
|
||||
createdAt: '2023-08-01T00:00:00.000Z',
|
||||
mime: 'image/png',
|
||||
name: 'Asset 1',
|
||||
updatedAt: '2023-08-01T00:00:00.000Z',
|
||||
url: '/assets/1',
|
||||
folder: null,
|
||||
folderPath: '/',
|
||||
hash: 'hash1',
|
||||
provider: 'local',
|
||||
},
|
||||
|
||||
{
|
||||
id: 2,
|
||||
type: 'asset',
|
||||
size: 200,
|
||||
createdAt: '2023-08-01T00:00:00.000Z',
|
||||
mime: 'image/png',
|
||||
name: 'Asset 2',
|
||||
updatedAt: '2023-08-01T00:00:00.000Z',
|
||||
url: '/assets/2',
|
||||
folder: null,
|
||||
folderPath: '/',
|
||||
hash: 'hash2',
|
||||
provider: 'local',
|
||||
},
|
||||
];
|
||||
|
||||
const FIXTURE_FOLDERS = [
|
||||
const FIXTURE_FOLDERS: FolderWithType[] = [
|
||||
{
|
||||
id: 11,
|
||||
type: 'folder',
|
||||
name: 'Folder 1',
|
||||
path: '/11',
|
||||
pathId: 11,
|
||||
},
|
||||
|
||||
{
|
||||
id: 12,
|
||||
type: 'folder',
|
||||
name: 'Folder 2',
|
||||
path: '/12',
|
||||
pathId: 12,
|
||||
},
|
||||
];
|
||||
|
||||
@ -33,7 +58,14 @@ jest.mock('@strapi/admin/strapi-admin', () => ({
|
||||
...jest.requireActual('@strapi/admin/strapi-admin'),
|
||||
useFetchClient: jest.fn().mockReturnValue({
|
||||
post: jest.fn((url, payload) => {
|
||||
const res = { data: { data: {} } };
|
||||
const res: { data: { data: { files: FileWithType[]; folders: FolderWithType[] } } } = {
|
||||
data: {
|
||||
data: {
|
||||
files: [],
|
||||
folders: [],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
if (payload?.fileIds) {
|
||||
res.data.data.files = FIXTURE_ASSETS;
|
||||
@ -48,7 +80,7 @@ jest.mock('@strapi/admin/strapi-admin', () => ({
|
||||
}),
|
||||
}));
|
||||
|
||||
function setup(...args) {
|
||||
function setup(...args: Parameters<typeof useBulkMove>) {
|
||||
return renderHook(() => useBulkMove(...args));
|
||||
}
|
||||
|
||||
@ -2,16 +2,26 @@ import { useFetchClient } from '@strapi/admin/strapi-admin';
|
||||
import { act, renderHook, screen } from '@tests/utils';
|
||||
|
||||
import { useBulkRemove } from '../useBulkRemove';
|
||||
import { BulkDeleteFiles } from '../../../../shared/contracts/files';
|
||||
import { BulkDeleteFolders } from '../../../../shared/contracts/folders';
|
||||
|
||||
const FIXTURE_ASSETS = [
|
||||
{
|
||||
id: 1,
|
||||
type: 'asset',
|
||||
name: 'asset1',
|
||||
path: 'path/to/asset1',
|
||||
pathId: 1,
|
||||
hash: 'hash1',
|
||||
},
|
||||
|
||||
{
|
||||
id: 2,
|
||||
type: 'asset',
|
||||
name: 'asset2',
|
||||
path: 'path/to/asset2',
|
||||
pathId: 2,
|
||||
hash: 'hash2',
|
||||
},
|
||||
];
|
||||
|
||||
@ -19,11 +29,17 @@ const FIXTURE_FOLDERS = [
|
||||
{
|
||||
id: 11,
|
||||
type: 'folder',
|
||||
name: 'folder1',
|
||||
path: 'path/to/folder1',
|
||||
pathId: 11,
|
||||
},
|
||||
|
||||
{
|
||||
id: 12,
|
||||
type: 'folder',
|
||||
name: 'folder2',
|
||||
path: 'path/to/folder2',
|
||||
pathId: 12,
|
||||
},
|
||||
];
|
||||
|
||||
@ -31,7 +47,14 @@ jest.mock('@strapi/admin/strapi-admin', () => ({
|
||||
...jest.requireActual('@strapi/admin/strapi-admin'),
|
||||
useFetchClient: jest.fn().mockReturnValue({
|
||||
post: jest.fn((url, payload) => {
|
||||
const res = { data: { data: {} } };
|
||||
const res: BulkDeleteFiles.Response | BulkDeleteFolders.Response = {
|
||||
data: {
|
||||
data: {
|
||||
files: [],
|
||||
folders: [],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
if (payload?.fileIds) {
|
||||
res.data.data.files = FIXTURE_ASSETS;
|
||||
@ -46,7 +69,7 @@ jest.mock('@strapi/admin/strapi-admin', () => ({
|
||||
}),
|
||||
}));
|
||||
|
||||
function setup(...args) {
|
||||
function setup(...args: Parameters<typeof useBulkRemove>) {
|
||||
return renderHook(() => useBulkRemove(...args));
|
||||
}
|
||||
|
||||
@ -22,7 +22,7 @@ jest.mock('@strapi/admin/strapi-admin', () => ({
|
||||
}),
|
||||
}));
|
||||
|
||||
function setup(...args) {
|
||||
function setup(...args: Parameters<typeof useEditFolder>) {
|
||||
return renderHook(() => useEditFolder(...args));
|
||||
}
|
||||
|
||||
@ -1,7 +1,5 @@
|
||||
import React from 'react';
|
||||
|
||||
import { useFetchClient } from '@strapi/admin/strapi-admin';
|
||||
import { act, renderHook, waitFor } from '@testing-library/react';
|
||||
import { act, renderHook, waitFor, RenderHookResult } from '@testing-library/react';
|
||||
import { IntlProvider } from 'react-intl';
|
||||
import { QueryClient, QueryClientProvider } from 'react-query';
|
||||
|
||||
@ -45,7 +43,7 @@ const client = new QueryClient({
|
||||
});
|
||||
|
||||
// eslint-disable-next-line react/prop-types
|
||||
function ComponentFixture({ children }) {
|
||||
function ComponentFixture({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<QueryClientProvider client={client}>
|
||||
<IntlProvider locale="en" messages={{}}>
|
||||
@ -55,7 +53,11 @@ function ComponentFixture({ children }) {
|
||||
);
|
||||
}
|
||||
|
||||
function setup(...args) {
|
||||
function setup(
|
||||
...args: Parameters<typeof useFolderStructure>
|
||||
): Promise<
|
||||
RenderHookResult<ReturnType<typeof useFolderStructure>, Parameters<typeof useFolderStructure>>
|
||||
> {
|
||||
return new Promise((resolve) => {
|
||||
act(() => {
|
||||
resolve(renderHook(() => useFolderStructure(...args), { wrapper: ComponentFixture }));
|
||||
@ -61,7 +61,9 @@ describe('useFolders', () => {
|
||||
]
|
||||
`);
|
||||
|
||||
expect(result.current.data[0].name).toBe('something');
|
||||
if (result.current.data) {
|
||||
expect(result.current.data[0].name).toBe('something');
|
||||
}
|
||||
});
|
||||
|
||||
test('fetches data from the right URL if a query param was set', async () => {
|
||||
@ -102,13 +104,13 @@ describe('useFolders', () => {
|
||||
]
|
||||
`);
|
||||
|
||||
result.current.data.forEach((folder) => {
|
||||
result.current.data?.forEach((folder) => {
|
||||
/**
|
||||
* We're passing a "current folder" in the query, which means
|
||||
* any folders returned should include the current folder's ID
|
||||
* in it's path because this get's the children of current.
|
||||
*/
|
||||
expect(folder.path.includes('1')).toBe(true);
|
||||
expect(folder.path?.includes('1')).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
@ -33,7 +33,7 @@ describe('useModalQueryParams', () => {
|
||||
onChangeSearch: expect.any(Function),
|
||||
});
|
||||
|
||||
await waitFor(() => expect(result.current[0].queryObject.pageSize).toBe(20));
|
||||
await waitFor(() => expect(result.current[0]?.queryObject?.pageSize).toBe(20));
|
||||
});
|
||||
|
||||
test('handles initial state', async () => {
|
||||
@ -44,16 +44,16 @@ describe('useModalQueryParams', () => {
|
||||
state: true,
|
||||
});
|
||||
|
||||
await waitFor(() => expect(result.current[0].queryObject.pageSize).toBe(20));
|
||||
await waitFor(() => expect(result.current[0]?.queryObject?.pageSize).toBe(20));
|
||||
});
|
||||
|
||||
test('onChangeFilters', async () => {
|
||||
const { result } = renderHook(() => useModalQueryParams());
|
||||
|
||||
await waitFor(() => expect(result.current[0].queryObject.pageSize).toBe(20));
|
||||
await waitFor(() => expect(result.current[0]?.queryObject?.pageSize).toBe(20));
|
||||
|
||||
act(() => {
|
||||
result.current[1].onChangeFilters([{ some: 'thing' }]);
|
||||
result.current[1]?.onChangeFilters?.([{ some: 'thing' }]);
|
||||
});
|
||||
|
||||
expect(result.current[0].queryObject).toStrictEqual({
|
||||
@ -73,10 +73,10 @@ describe('useModalQueryParams', () => {
|
||||
test('onChangeFolder', async () => {
|
||||
const { result } = renderHook(() => useModalQueryParams());
|
||||
|
||||
await waitFor(() => expect(result.current[0].queryObject.pageSize).toBe(20));
|
||||
await waitFor(() => expect(result.current[0]?.queryObject?.pageSize).toBe(20));
|
||||
|
||||
act(() => {
|
||||
result.current[1].onChangeFolder({ id: 1 }, '/1');
|
||||
result.current[1]?.onChangeFolder?.({ id: 1 }, '/1');
|
||||
});
|
||||
|
||||
expect(result.current[0].queryObject).toStrictEqual({
|
||||
@ -92,10 +92,10 @@ describe('useModalQueryParams', () => {
|
||||
test('onChangePage', async () => {
|
||||
const { result } = renderHook(() => useModalQueryParams());
|
||||
|
||||
await waitFor(() => expect(result.current[0].queryObject.pageSize).toBe(20));
|
||||
await waitFor(() => expect(result.current[0]?.queryObject?.pageSize).toBe(20));
|
||||
|
||||
act(() => {
|
||||
result.current[1].onChangePage({ id: 1 });
|
||||
result.current[1]?.onChangePage?.({ id: 1 });
|
||||
});
|
||||
|
||||
expect(result.current[0].queryObject).toStrictEqual({
|
||||
@ -110,10 +110,10 @@ describe('useModalQueryParams', () => {
|
||||
test('onChangePageSize', async () => {
|
||||
const { result } = renderHook(() => useModalQueryParams());
|
||||
|
||||
await waitFor(() => expect(result.current[0].queryObject.pageSize).toBe(20));
|
||||
await waitFor(() => expect(result.current[0]?.queryObject?.pageSize).toBe(20));
|
||||
|
||||
act(() => {
|
||||
result.current[1].onChangePageSize(5);
|
||||
result.current[1]?.onChangePageSize?.(5);
|
||||
});
|
||||
|
||||
expect(result.current[0].queryObject).toStrictEqual({
|
||||
@ -125,10 +125,10 @@ describe('useModalQueryParams', () => {
|
||||
test('onChangePageSize - converts string to numbers', async () => {
|
||||
const { result } = renderHook(() => useModalQueryParams());
|
||||
|
||||
await waitFor(() => expect(result.current[0].queryObject.pageSize).toBe(20));
|
||||
await waitFor(() => expect(result.current[0]?.queryObject?.pageSize).toBe(20));
|
||||
|
||||
act(() => {
|
||||
result.current[1].onChangePageSize('5');
|
||||
result.current[1]?.onChangePageSize?.('5');
|
||||
});
|
||||
|
||||
expect(result.current[0].queryObject).toStrictEqual({
|
||||
@ -140,26 +140,26 @@ describe('useModalQueryParams', () => {
|
||||
test('onChangeSort', async () => {
|
||||
const { result } = renderHook(() => useModalQueryParams());
|
||||
|
||||
await waitFor(() => expect(result.current[0].queryObject.pageSize).toBe(20));
|
||||
await waitFor(() => expect(result.current[0]?.queryObject?.pageSize).toBe(20));
|
||||
|
||||
act(() => {
|
||||
result.current[1].onChangeSort('something:else');
|
||||
result.current[1]?.onChangeSort?.('name:DESC');
|
||||
});
|
||||
|
||||
expect(result.current[0].queryObject).toStrictEqual({
|
||||
...FIXTURE_QUERY,
|
||||
pageSize: 20,
|
||||
sort: 'something:else',
|
||||
sort: 'name:DESC',
|
||||
});
|
||||
});
|
||||
|
||||
test('onChangeSearch', async () => {
|
||||
const { result } = renderHook(() => useModalQueryParams());
|
||||
|
||||
await waitFor(() => expect(result.current[0].queryObject.pageSize).toBe(20));
|
||||
await waitFor(() => expect(result.current[0]?.queryObject?.pageSize).toBe(20));
|
||||
|
||||
act(() => {
|
||||
result.current[1].onChangeSearch('something');
|
||||
result.current[1]?.onChangeSearch?.('something');
|
||||
});
|
||||
|
||||
expect(result.current[0].queryObject).toStrictEqual({
|
||||
@ -172,18 +172,18 @@ describe('useModalQueryParams', () => {
|
||||
test('onChangeSearch - empty string resets all values and removes _q and page', async () => {
|
||||
const { result } = renderHook(() => useModalQueryParams());
|
||||
|
||||
await waitFor(() => expect(result.current[0].queryObject.pageSize).toBe(20));
|
||||
await waitFor(() => expect(result.current[0]?.queryObject?.pageSize).toBe(20));
|
||||
|
||||
act(() => {
|
||||
result.current[1].onChangePage({ id: 1 });
|
||||
result.current[1]?.onChangePage?.({ id: 1 });
|
||||
});
|
||||
|
||||
act(() => {
|
||||
result.current[1].onChangeSearch('something');
|
||||
result.current[1]?.onChangeSearch?.('something');
|
||||
});
|
||||
|
||||
act(() => {
|
||||
result.current[1].onChangeSearch('');
|
||||
result.current[1]?.onChangeSearch?.('');
|
||||
});
|
||||
|
||||
expect(result.current[0].queryObject).toStrictEqual({
|
||||
@ -1,5 +1,3 @@
|
||||
import React from 'react';
|
||||
|
||||
import { NotificationsProvider, useNotification } from '@strapi/admin/strapi-admin';
|
||||
import { DesignSystemProvider } from '@strapi/design-system';
|
||||
import { act, renderHook, waitFor } from '@testing-library/react';
|
||||
@ -40,8 +38,7 @@ const client = new QueryClient({
|
||||
},
|
||||
});
|
||||
|
||||
// eslint-disable-next-line react/prop-types
|
||||
function ComponentFixture({ children }) {
|
||||
function ComponentFixture({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<QueryClientProvider client={client}>
|
||||
<DesignSystemProvider>
|
||||
@ -55,7 +52,7 @@ function ComponentFixture({ children }) {
|
||||
);
|
||||
}
|
||||
|
||||
function setup(...args) {
|
||||
function setup(...args: Parameters<typeof useRemoveAsset>) {
|
||||
return new Promise((resolve) => {
|
||||
act(() => {
|
||||
resolve(renderHook(() => useRemoveAsset(...args), { wrapper: ComponentFixture }));
|
||||
@ -72,7 +69,7 @@ describe('useRemoveAsset', () => {
|
||||
const { toggleNotification } = useNotification();
|
||||
const {
|
||||
result: { current },
|
||||
} = await setup(jest.fn);
|
||||
} = (await setup(jest.fn)) as { result: { current: any } };
|
||||
const { removeAsset } = current;
|
||||
|
||||
try {
|
||||
@ -92,7 +89,7 @@ describe('useRemoveAsset', () => {
|
||||
const queryClient = useQueryClient();
|
||||
const {
|
||||
result: { current },
|
||||
} = await setup(jest.fn);
|
||||
} = (await setup(jest.fn)) as { result: { current: any } };
|
||||
const { removeAsset } = current;
|
||||
|
||||
await act(async () => {
|
||||
@ -118,7 +115,8 @@ describe('useRemoveAsset', () => {
|
||||
const { toggleNotification } = useNotification();
|
||||
const {
|
||||
result: { current },
|
||||
} = await setup();
|
||||
// @ts-expect-error We are checking the error case
|
||||
} = (await setup()) as { result: { current: any } };
|
||||
const { removeAsset } = current;
|
||||
|
||||
try {
|
||||
@ -1,20 +1,26 @@
|
||||
import { useEffect } from 'react';
|
||||
import * as React from 'react';
|
||||
|
||||
import { useNotification, useFetchClient } from '@strapi/admin/strapi-admin';
|
||||
import { useNotifyAT } from '@strapi/design-system';
|
||||
import { useIntl } from 'react-intl';
|
||||
import { useQuery } from 'react-query';
|
||||
import { Query, GetFiles } from '../../../shared/contracts/files';
|
||||
|
||||
import pluginId from '../pluginId';
|
||||
|
||||
export const useAssets = ({ skipWhen = false, query = {} } = {}) => {
|
||||
interface UseAssetsOptions {
|
||||
skipWhen?: boolean;
|
||||
query?: Query;
|
||||
}
|
||||
|
||||
export const useAssets = ({ skipWhen = false, query = {} }: UseAssetsOptions = {}) => {
|
||||
const { formatMessage } = useIntl();
|
||||
const { toggleNotification } = useNotification();
|
||||
const { notifyStatus } = useNotifyAT();
|
||||
const { get } = useFetchClient();
|
||||
const { folderPath, _q, ...paramsExceptFolderAndQ } = query;
|
||||
|
||||
let params;
|
||||
let params: Query;
|
||||
|
||||
if (_q) {
|
||||
params = {
|
||||
@ -35,7 +41,10 @@ export const useAssets = ({ skipWhen = false, query = {} } = {}) => {
|
||||
};
|
||||
}
|
||||
|
||||
const { data, error, isLoading } = useQuery(
|
||||
const { data, error, isLoading } = useQuery<
|
||||
GetFiles.Response['data'],
|
||||
GetFiles.Response['error']
|
||||
>(
|
||||
[pluginId, 'assets', params],
|
||||
async () => {
|
||||
const { data } = await get('/upload/files', { params });
|
||||
@ -74,7 +83,7 @@ export const useAssets = ({ skipWhen = false, query = {} } = {}) => {
|
||||
}
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
React.useEffect(() => {
|
||||
if (data) {
|
||||
notifyStatus(
|
||||
formatMessage({
|
||||
@ -85,7 +94,7 @@ export const useAssets = ({ skipWhen = false, query = {} } = {}) => {
|
||||
}
|
||||
}, [data, formatMessage, notifyStatus]);
|
||||
|
||||
useEffect(() => {
|
||||
React.useEffect(() => {
|
||||
if (error) {
|
||||
toggleNotification({
|
||||
type: 'danger',
|
||||
@ -1,18 +1,39 @@
|
||||
import { useNotification, useFetchClient } from '@strapi/admin/strapi-admin';
|
||||
import { useIntl } from 'react-intl';
|
||||
import { useMutation, useQueryClient } from 'react-query';
|
||||
import { File, BulkMoveFiles } from '../../../shared/contracts/files';
|
||||
import { Folder, BulkMoveFolders } from '../../../shared/contracts/folders';
|
||||
|
||||
import pluginId from '../pluginId';
|
||||
import { getTrad } from '../utils';
|
||||
|
||||
export interface FolderWithType extends Folder {
|
||||
type: string;
|
||||
}
|
||||
|
||||
export interface FileWithType extends File {
|
||||
type: string;
|
||||
}
|
||||
|
||||
interface BulkMoveParams {
|
||||
destinationFolderId: number;
|
||||
filesAndFolders: Array<FolderWithType | FileWithType>;
|
||||
}
|
||||
|
||||
// Define the shape of the accumulator object
|
||||
type Payload = {
|
||||
fileIds?: number[];
|
||||
folderIds?: number[];
|
||||
};
|
||||
|
||||
export const useBulkMove = () => {
|
||||
const { formatMessage } = useIntl();
|
||||
const { toggleNotification } = useNotification();
|
||||
const queryClient = useQueryClient();
|
||||
const { post } = useFetchClient();
|
||||
|
||||
const bulkMoveQuery = ({ destinationFolderId, filesAndFolders }) => {
|
||||
const payload = filesAndFolders.reduce((acc, selected) => {
|
||||
const bulkMoveQuery = ({ destinationFolderId, filesAndFolders }: BulkMoveParams) => {
|
||||
const payload = filesAndFolders.reduce<Payload>((acc, selected) => {
|
||||
const { id, type } = selected;
|
||||
const key = type === 'asset' ? 'fileIds' : 'folderIds';
|
||||
|
||||
@ -20,7 +41,7 @@ export const useBulkMove = () => {
|
||||
acc[key] = [];
|
||||
}
|
||||
|
||||
acc[key].push(id);
|
||||
acc[key]!.push(id);
|
||||
|
||||
return acc;
|
||||
}, {});
|
||||
@ -28,7 +49,11 @@ export const useBulkMove = () => {
|
||||
return post('/upload/actions/bulk-move', { ...payload, destinationFolderId });
|
||||
};
|
||||
|
||||
const mutation = useMutation(bulkMoveQuery, {
|
||||
const mutation = useMutation<
|
||||
BulkMoveFolders.Response | BulkMoveFiles.Response,
|
||||
BulkMoveFolders.Response['error'] | BulkMoveFiles.Response['error'],
|
||||
BulkMoveParams
|
||||
>(bulkMoveQuery, {
|
||||
onSuccess(res) {
|
||||
const {
|
||||
data: { data },
|
||||
@ -53,8 +78,10 @@ export const useBulkMove = () => {
|
||||
},
|
||||
});
|
||||
|
||||
const move = (destinationFolderId, filesAndFolders) =>
|
||||
mutation.mutateAsync({ destinationFolderId, filesAndFolders });
|
||||
const move = (
|
||||
destinationFolderId: number,
|
||||
filesAndFolders: Array<FolderWithType | FileWithType>
|
||||
) => mutation.mutateAsync({ destinationFolderId, filesAndFolders });
|
||||
|
||||
return { ...mutation, move };
|
||||
};
|
||||
@ -4,6 +4,19 @@ import { useMutation, useQueryClient } from 'react-query';
|
||||
|
||||
import pluginId from '../pluginId';
|
||||
import { getTrad } from '../utils';
|
||||
import { BulkDeleteFiles, File } from '../../../shared/contracts/files';
|
||||
import type { BulkDeleteFolders, Folder } from '../../../shared/contracts/folders';
|
||||
|
||||
export interface FileWithType extends File {
|
||||
type: string;
|
||||
}
|
||||
|
||||
export interface FolderWithType extends Folder {
|
||||
type: string;
|
||||
}
|
||||
|
||||
type BulkRemovePayload = Partial<BulkDeleteFiles.Request['body']> &
|
||||
Partial<BulkDeleteFolders.Request['body']>;
|
||||
|
||||
export const useBulkRemove = () => {
|
||||
const { toggleNotification } = useNotification();
|
||||
@ -11,8 +24,8 @@ export const useBulkRemove = () => {
|
||||
const queryClient = useQueryClient();
|
||||
const { post } = useFetchClient();
|
||||
|
||||
const bulkRemoveQuery = (filesAndFolders) => {
|
||||
const payload = filesAndFolders.reduce((acc, selected) => {
|
||||
const bulkRemoveQuery = (filesAndFolders: Array<FileWithType | FolderWithType>) => {
|
||||
const payload = filesAndFolders.reduce<BulkRemovePayload>((acc, selected) => {
|
||||
const { id, type } = selected;
|
||||
const key = type === 'asset' ? 'fileIds' : 'folderIds';
|
||||
|
||||
@ -20,7 +33,7 @@ export const useBulkRemove = () => {
|
||||
acc[key] = [];
|
||||
}
|
||||
|
||||
acc[key].push(id);
|
||||
acc[key]!.push(id);
|
||||
|
||||
return acc;
|
||||
}, {});
|
||||
@ -28,7 +41,11 @@ export const useBulkRemove = () => {
|
||||
return post('/upload/actions/bulk-delete', payload);
|
||||
};
|
||||
|
||||
const mutation = useMutation(bulkRemoveQuery, {
|
||||
const mutation = useMutation<
|
||||
BulkDeleteFiles.Response | BulkDeleteFolders.Response,
|
||||
BulkDeleteFiles.Response['error'] | BulkDeleteFolders.Response['error'],
|
||||
Array<FileWithType | FolderWithType>
|
||||
>(bulkRemoveQuery, {
|
||||
onSuccess(res) {
|
||||
const {
|
||||
data: { data },
|
||||
@ -52,11 +69,12 @@ export const useBulkRemove = () => {
|
||||
});
|
||||
},
|
||||
onError(error) {
|
||||
toggleNotification({ type: 'danger', message: error.message });
|
||||
toggleNotification({ type: 'danger', message: error?.message });
|
||||
},
|
||||
});
|
||||
|
||||
const remove = (...args) => mutation.mutateAsync(...args);
|
||||
const remove = (...args: Parameters<typeof mutation.mutateAsync>) =>
|
||||
mutation.mutateAsync(...args);
|
||||
|
||||
return { ...mutation, remove };
|
||||
};
|
||||
@ -1,6 +1,7 @@
|
||||
import { useTracking, useNotification, useFetchClient } from '@strapi/admin/strapi-admin';
|
||||
import { useIntl } from 'react-intl';
|
||||
import { useMutation, useQuery } from 'react-query';
|
||||
import { useMutation, useQuery, UseMutationResult, UseQueryResult } from 'react-query';
|
||||
import { GetConfiguration, UpdateConfiguration } from '../../../shared/contracts/configuration';
|
||||
|
||||
import pluginId from '../pluginId';
|
||||
|
||||
@ -13,10 +14,12 @@ export const useConfig = () => {
|
||||
const { toggleNotification } = useNotification();
|
||||
const { get, put } = useFetchClient();
|
||||
|
||||
const config = useQuery(
|
||||
const config: UseQueryResult<
|
||||
GetConfiguration.Response['data']['data'] | GetConfiguration.Response['error']
|
||||
> = useQuery(
|
||||
queryKey,
|
||||
async () => {
|
||||
const res = await get(endpoint);
|
||||
const res: GetConfiguration.Response = await get(endpoint);
|
||||
|
||||
return res.data.data;
|
||||
},
|
||||
@ -30,13 +33,17 @@ export const useConfig = () => {
|
||||
/**
|
||||
* We're cementing that we always expect an object to be returned.
|
||||
*/
|
||||
select: (data) => (!data ? {} : data),
|
||||
select: (data) => data || {},
|
||||
}
|
||||
);
|
||||
|
||||
const putMutation = useMutation(
|
||||
const putMutation: UseMutationResult<
|
||||
void,
|
||||
UpdateConfiguration.Response['error'],
|
||||
UpdateConfiguration.Request['body']
|
||||
> = useMutation(
|
||||
async (body) => {
|
||||
await put(endpoint, body);
|
||||
await put<UpdateConfiguration.Response>(endpoint, body);
|
||||
},
|
||||
{
|
||||
onSuccess() {
|
||||
@ -1,15 +1,27 @@
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import * as React from 'react';
|
||||
|
||||
import Cropper from 'cropperjs';
|
||||
|
||||
const QUALITY = 1;
|
||||
|
||||
export const useCropImg = () => {
|
||||
const cropperRef = useRef();
|
||||
const [isCropping, setIsCropping] = useState(false);
|
||||
const [size, setSize] = useState({ width: undefined, height: undefined });
|
||||
type Size = {
|
||||
width?: number;
|
||||
height?: number;
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
type Resize = {
|
||||
detail: {
|
||||
height: number;
|
||||
width: number;
|
||||
};
|
||||
};
|
||||
|
||||
export const useCropImg = () => {
|
||||
const cropperRef = React.useRef<Cropper>();
|
||||
const [isCropping, setIsCropping] = React.useState(false);
|
||||
const [size, setSize] = React.useState<Size>({ width: undefined, height: undefined });
|
||||
|
||||
React.useEffect(() => {
|
||||
return () => {
|
||||
if (cropperRef.current) {
|
||||
cropperRef.current.destroy();
|
||||
@ -17,14 +29,14 @@ export const useCropImg = () => {
|
||||
};
|
||||
}, []);
|
||||
|
||||
const handleResize = ({ detail: { height, width } }) => {
|
||||
const handleResize = ({ detail: { height, width } }: Resize) => {
|
||||
const roundedDataWidth = Math.round(width);
|
||||
const roundedDataHeight = Math.round(height);
|
||||
|
||||
setSize({ width: roundedDataWidth, height: roundedDataHeight });
|
||||
};
|
||||
|
||||
const crop = (image) => {
|
||||
const crop = (image: HTMLImageElement) => {
|
||||
if (!cropperRef.current) {
|
||||
cropperRef.current = new Cropper(image, {
|
||||
modal: true,
|
||||
@ -48,7 +60,7 @@ export const useCropImg = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const produceFile = (name, mimeType, lastModifiedDate) =>
|
||||
const produceFile = (name: string, mimeType: string, lastModifiedDate: string) =>
|
||||
new Promise((resolve, reject) => {
|
||||
if (!cropperRef.current) {
|
||||
reject(
|
||||
@ -62,9 +74,9 @@ export const useCropImg = () => {
|
||||
canvas.toBlob(
|
||||
(blob) => {
|
||||
resolve(
|
||||
new File([blob], name, {
|
||||
new File([blob!], name, {
|
||||
type: mimeType,
|
||||
lastModifiedDate,
|
||||
lastModified: new Date(lastModifiedDate).getTime(),
|
||||
})
|
||||
);
|
||||
},
|
||||
@ -1,74 +0,0 @@
|
||||
import { useState } from 'react';
|
||||
|
||||
import { useNotification, useFetchClient } from '@strapi/admin/strapi-admin';
|
||||
import { useIntl } from 'react-intl';
|
||||
import { useMutation, useQueryClient } from 'react-query';
|
||||
|
||||
import pluginId from '../pluginId';
|
||||
import { getTrad } from '../utils';
|
||||
|
||||
const editAssetRequest = (asset, file, signal, onProgress, post) => {
|
||||
const endpoint = `/${pluginId}?id=${asset.id}`;
|
||||
|
||||
const formData = new FormData();
|
||||
|
||||
if (file) {
|
||||
formData.append('files', file);
|
||||
}
|
||||
|
||||
formData.append(
|
||||
'fileInfo',
|
||||
JSON.stringify({
|
||||
alternativeText: asset.alternativeText,
|
||||
caption: asset.caption,
|
||||
folder: asset.folder,
|
||||
name: asset.name,
|
||||
})
|
||||
);
|
||||
|
||||
/**
|
||||
* onProgress is not possible using native fetch
|
||||
* need to look into an alternative to make it work
|
||||
* perhaps using xhr like Axios does
|
||||
*/
|
||||
return post(endpoint, formData, {
|
||||
signal,
|
||||
}).then((res) => res.data);
|
||||
};
|
||||
|
||||
export const useEditAsset = () => {
|
||||
const [progress, setProgress] = useState(0);
|
||||
const { formatMessage } = useIntl();
|
||||
const { toggleNotification } = useNotification();
|
||||
const queryClient = useQueryClient();
|
||||
const abortController = new AbortController();
|
||||
const signal = abortController.signal;
|
||||
const { post } = useFetchClient();
|
||||
|
||||
const mutation = useMutation(
|
||||
({ asset, file }) => editAssetRequest(asset, file, signal, setProgress, post),
|
||||
{
|
||||
onSuccess() {
|
||||
queryClient.refetchQueries([pluginId, 'assets'], { active: true });
|
||||
queryClient.refetchQueries([pluginId, 'asset-count'], { active: true });
|
||||
queryClient.refetchQueries([pluginId, 'folders'], { active: true });
|
||||
},
|
||||
onError(reason) {
|
||||
if (reason.response.status === 403) {
|
||||
toggleNotification({
|
||||
type: 'info',
|
||||
message: formatMessage({ id: getTrad('permissions.not-allowed.update') }),
|
||||
});
|
||||
} else {
|
||||
toggleNotification({ type: 'danger', message: reason.message });
|
||||
}
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
const editAsset = (asset, file) => mutation.mutateAsync({ asset, file });
|
||||
|
||||
const cancel = () => abortController.abort();
|
||||
|
||||
return { ...mutation, cancel, editAsset, progress, status: mutation.status };
|
||||
};
|
||||
92
packages/core/upload/admin/src/hooks/useEditAsset.ts
Normal file
92
packages/core/upload/admin/src/hooks/useEditAsset.ts
Normal file
@ -0,0 +1,92 @@
|
||||
import * as React from 'react';
|
||||
|
||||
import { useNotification, useFetchClient, FetchClient } from '@strapi/admin/strapi-admin';
|
||||
import { useIntl } from 'react-intl';
|
||||
import { useMutation, useQueryClient } from 'react-query';
|
||||
import { UpdateFile, File as FileAsset } from '../../../shared/contracts/files';
|
||||
|
||||
import pluginId from '../pluginId';
|
||||
import { getTrad } from '../utils';
|
||||
|
||||
export type ErrorMutation = {
|
||||
message: string;
|
||||
response: {
|
||||
status: number;
|
||||
data: {
|
||||
error: Error;
|
||||
};
|
||||
};
|
||||
} | null;
|
||||
|
||||
const editAssetRequest = (
|
||||
asset: FileAsset,
|
||||
file: File,
|
||||
signal: AbortSignal,
|
||||
onProgress: (progress: number) => void,
|
||||
post: FetchClient['post']
|
||||
) => {
|
||||
const endpoint = `/${pluginId}?id=${asset.id}`;
|
||||
|
||||
const formData = new FormData();
|
||||
|
||||
if (file) {
|
||||
formData.append('files', file);
|
||||
}
|
||||
|
||||
formData.append(
|
||||
'fileInfo',
|
||||
JSON.stringify({
|
||||
alternativeText: asset.alternativeText,
|
||||
caption: asset.caption,
|
||||
folder: asset.folder,
|
||||
name: asset.name,
|
||||
})
|
||||
);
|
||||
|
||||
/**
|
||||
* onProgress is not possible using native fetch
|
||||
* need to look into an alternative to make it work
|
||||
* perhaps using xhr like Axios does
|
||||
*/
|
||||
return post(endpoint, formData, {
|
||||
signal,
|
||||
}).then((res) => res.data);
|
||||
};
|
||||
|
||||
export const useEditAsset = () => {
|
||||
const [progress, setProgress] = React.useState(0);
|
||||
const { formatMessage } = useIntl();
|
||||
const { toggleNotification } = useNotification();
|
||||
const queryClient = useQueryClient();
|
||||
const abortController = new AbortController();
|
||||
const signal = abortController.signal;
|
||||
const { post } = useFetchClient();
|
||||
|
||||
const mutation = useMutation<
|
||||
UpdateFile.Response['data'],
|
||||
ErrorMutation,
|
||||
{ asset: FileAsset; file: File }
|
||||
>(({ asset, file }) => editAssetRequest(asset, file, signal, setProgress, post), {
|
||||
onSuccess() {
|
||||
queryClient.refetchQueries([pluginId, 'assets'], { active: true });
|
||||
queryClient.refetchQueries([pluginId, 'asset-count'], { active: true });
|
||||
queryClient.refetchQueries([pluginId, 'folders'], { active: true });
|
||||
},
|
||||
onError(reason) {
|
||||
if (reason?.response?.status === 403) {
|
||||
toggleNotification({
|
||||
type: 'info',
|
||||
message: formatMessage({ id: getTrad('permissions.not-allowed.update') }),
|
||||
});
|
||||
} else {
|
||||
toggleNotification({ type: 'danger', message: reason?.message });
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
const editAsset = (asset: FileAsset, file: File) => mutation.mutateAsync({ asset, file });
|
||||
|
||||
const cancel = () => abortController.abort();
|
||||
|
||||
return { ...mutation, cancel, editAsset, progress, status: mutation.status };
|
||||
};
|
||||
@ -1,27 +0,0 @@
|
||||
import { useFetchClient } from '@strapi/admin/strapi-admin';
|
||||
import { useMutation, useQueryClient } from 'react-query';
|
||||
|
||||
import pluginId from '../pluginId';
|
||||
|
||||
const editFolderRequest = (put, post, { attrs, id }) => {
|
||||
const isEditing = !!id;
|
||||
const method = isEditing ? put : post;
|
||||
|
||||
return method(`/upload/folders/${id ?? ''}`, attrs).then((res) => res.data);
|
||||
};
|
||||
|
||||
export const useEditFolder = () => {
|
||||
const queryClient = useQueryClient();
|
||||
const { put, post } = useFetchClient();
|
||||
|
||||
const mutation = useMutation((...args) => editFolderRequest(put, post, ...args), {
|
||||
onSuccess() {
|
||||
queryClient.refetchQueries([pluginId, 'folders'], { active: true });
|
||||
queryClient.refetchQueries([pluginId, 'folder', 'structure'], { active: true });
|
||||
},
|
||||
});
|
||||
|
||||
const editFolder = (attrs, id) => mutation.mutateAsync({ attrs, id });
|
||||
|
||||
return { ...mutation, editFolder, status: mutation.status };
|
||||
};
|
||||
44
packages/core/upload/admin/src/hooks/useEditFolder.ts
Normal file
44
packages/core/upload/admin/src/hooks/useEditFolder.ts
Normal file
@ -0,0 +1,44 @@
|
||||
import { useFetchClient, FetchClient } from '@strapi/admin/strapi-admin';
|
||||
import { useMutation, useQueryClient } from 'react-query';
|
||||
import { CreateFolders, UpdateFolder } from '../../../shared/contracts/folders';
|
||||
|
||||
import pluginId from '../pluginId';
|
||||
|
||||
interface EditFolderRequestParams {
|
||||
attrs: CreateFolders.Request['body'] | UpdateFolder.Request['body'];
|
||||
id?: UpdateFolder.Request['params']['id'];
|
||||
}
|
||||
|
||||
const editFolderRequest = (
|
||||
put: FetchClient['put'],
|
||||
post: FetchClient['post'],
|
||||
{ attrs, id }: EditFolderRequestParams
|
||||
): Promise<UpdateFolder.Response['data'] | CreateFolders.Response['data']> => {
|
||||
const isEditing = !!id;
|
||||
const method = isEditing ? put : post;
|
||||
|
||||
return method(`/upload/folders/${id ?? ''}`, attrs).then((res) => res.data);
|
||||
};
|
||||
|
||||
export const useEditFolder = () => {
|
||||
const queryClient = useQueryClient();
|
||||
const { put, post } = useFetchClient();
|
||||
|
||||
const mutation = useMutation<
|
||||
UpdateFolder.Response['data'] | CreateFolders.Response['data'],
|
||||
UpdateFolder.Response['error'] | CreateFolders.Response['error'],
|
||||
EditFolderRequestParams
|
||||
>((...args) => editFolderRequest(put, post, ...args), {
|
||||
onSuccess() {
|
||||
queryClient.refetchQueries([pluginId, 'folders'], { active: true });
|
||||
queryClient.refetchQueries([pluginId, 'folder', 'structure'], { active: true });
|
||||
},
|
||||
});
|
||||
|
||||
const editFolder = (
|
||||
attrs: EditFolderRequestParams['attrs'],
|
||||
id?: EditFolderRequestParams['id']
|
||||
) => mutation.mutateAsync({ attrs, id });
|
||||
|
||||
return { ...mutation, editFolder, status: mutation.status };
|
||||
};
|
||||
@ -4,13 +4,17 @@ import { useQuery } from 'react-query';
|
||||
|
||||
import pluginId from '../pluginId';
|
||||
import { getTrad } from '../utils';
|
||||
import { GetFolder } from '../../../shared/contracts/folders';
|
||||
|
||||
export const useFolder = (id, { enabled = true } = {}) => {
|
||||
export const useFolder = (id: number, { enabled = true } = {}) => {
|
||||
const { toggleNotification } = useNotification();
|
||||
const { get } = useFetchClient();
|
||||
const { formatMessage } = useIntl();
|
||||
|
||||
const { data, error, isLoading } = useQuery(
|
||||
const { data, error, isLoading } = useQuery<
|
||||
GetFolder.Response['data'],
|
||||
GetFolder.Response['error']
|
||||
>(
|
||||
[pluginId, 'folder', id],
|
||||
async () => {
|
||||
const {
|
||||
@ -7,7 +7,9 @@ import { getTrad } from '../utils';
|
||||
|
||||
import { recursiveRenameKeys } from './utils/rename-keys';
|
||||
|
||||
const FIELD_MAPPING = {
|
||||
import { FolderNode, GetFolderStructure } from '../../../shared/contracts/folders';
|
||||
|
||||
const FIELD_MAPPING: Record<string, string> = {
|
||||
name: 'label',
|
||||
id: 'value',
|
||||
};
|
||||
@ -19,9 +21,11 @@ export const useFolderStructure = ({ enabled = true } = {}) => {
|
||||
const fetchFolderStructure = async () => {
|
||||
const {
|
||||
data: { data },
|
||||
} = await get('/upload/folder-structure');
|
||||
} = await get<GetFolderStructure.Response['data']>('/upload/folder-structure');
|
||||
|
||||
const children = data.map((f) => recursiveRenameKeys(f, (key) => FIELD_MAPPING?.[key] ?? key));
|
||||
const children = data.map((f: FolderNode) =>
|
||||
recursiveRenameKeys(f, (key) => FIELD_MAPPING?.[key] ?? key)
|
||||
);
|
||||
|
||||
return [
|
||||
{
|
||||
@ -5,17 +5,24 @@ import { useNotifyAT } from '@strapi/design-system';
|
||||
import { stringify } from 'qs';
|
||||
import { useIntl } from 'react-intl';
|
||||
import { useQuery } from 'react-query';
|
||||
import { GetFolders } from '../../../shared/contracts/folders';
|
||||
import type { Query } from '../../../shared/contracts/files';
|
||||
|
||||
import pluginId from '../pluginId';
|
||||
|
||||
export const useFolders = ({ enabled = true, query = {} } = {}) => {
|
||||
interface UseFoldersOptions {
|
||||
enabled?: boolean;
|
||||
query?: Query;
|
||||
}
|
||||
|
||||
export const useFolders = ({ enabled = true, query = {} }: UseFoldersOptions = {}) => {
|
||||
const { formatMessage } = useIntl();
|
||||
const { toggleNotification } = useNotification();
|
||||
const { notifyStatus } = useNotifyAT();
|
||||
const { folder, _q, ...paramsExceptFolderAndQ } = query;
|
||||
const { get } = useFetchClient();
|
||||
|
||||
let params;
|
||||
let params: Query;
|
||||
|
||||
if (_q) {
|
||||
params = {
|
||||
@ -46,12 +53,15 @@ export const useFolders = ({ enabled = true, query = {} } = {}) => {
|
||||
};
|
||||
}
|
||||
|
||||
const { data, error, isLoading } = useQuery(
|
||||
const { data, error, isLoading } = useQuery<
|
||||
GetFolders.Response['data'],
|
||||
GetFolders.Response['error']
|
||||
>(
|
||||
[pluginId, 'folders', stringify(params)],
|
||||
async () => {
|
||||
const {
|
||||
data: { data },
|
||||
} = await get('/upload/folders', { params });
|
||||
} = await get<GetFolders.Response>('/upload/folders', { params });
|
||||
|
||||
return data;
|
||||
},
|
||||
@ -1,6 +1,7 @@
|
||||
import { useRBAC } from '@strapi/admin/strapi-admin';
|
||||
|
||||
import { PERMISSIONS } from '../constants';
|
||||
// TODO: replace this import with the import from constants file when it will be migrated to TS
|
||||
import { PERMISSIONS } from '../newConstants';
|
||||
|
||||
const { main, ...restPermissions } = PERMISSIONS;
|
||||
|
||||
@ -1,91 +0,0 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
import { useTracking } from '@strapi/admin/strapi-admin';
|
||||
import { stringify } from 'qs';
|
||||
|
||||
import { useConfig } from './useConfig';
|
||||
|
||||
const useModalQueryParams = (initialState) => {
|
||||
const { trackUsage } = useTracking();
|
||||
const {
|
||||
config: { data: config },
|
||||
} = useConfig();
|
||||
|
||||
const [queryObject, setQueryObject] = useState({
|
||||
page: 1,
|
||||
sort: 'updatedAt:DESC',
|
||||
pageSize: 10,
|
||||
filters: {
|
||||
$and: [],
|
||||
},
|
||||
...initialState,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (config) {
|
||||
setQueryObject((prevQuery) => ({
|
||||
...prevQuery,
|
||||
sort: config.sort,
|
||||
pageSize: config.pageSize,
|
||||
}));
|
||||
}
|
||||
}, [config]);
|
||||
|
||||
const handleChangeFilters = (nextFilters) => {
|
||||
trackUsage('didFilterMediaLibraryElements', {
|
||||
location: 'content-manager',
|
||||
filter: Object.keys(nextFilters[nextFilters.length - 1])[0],
|
||||
});
|
||||
setQueryObject((prev) => ({ ...prev, page: 1, filters: { $and: nextFilters } }));
|
||||
};
|
||||
|
||||
const handleChangePageSize = (pageSize) => {
|
||||
setQueryObject((prev) => ({ ...prev, pageSize: parseInt(pageSize, 10), page: 1 }));
|
||||
};
|
||||
|
||||
const handeChangePage = (page) => {
|
||||
setQueryObject((prev) => ({ ...prev, page }));
|
||||
};
|
||||
|
||||
const handleChangeSort = (sort) => {
|
||||
trackUsage('didSortMediaLibraryElements', {
|
||||
location: 'content-manager',
|
||||
sort,
|
||||
});
|
||||
setQueryObject((prev) => ({ ...prev, sort }));
|
||||
};
|
||||
|
||||
const handleChangeSearch = (_q) => {
|
||||
if (_q) {
|
||||
setQueryObject((prev) => ({ ...prev, _q, page: 1 }));
|
||||
} else {
|
||||
const newState = { page: 1 };
|
||||
|
||||
Object.keys(queryObject).forEach((key) => {
|
||||
if (!['page', '_q'].includes(key)) {
|
||||
newState[key] = queryObject[key];
|
||||
}
|
||||
});
|
||||
|
||||
setQueryObject(newState);
|
||||
}
|
||||
};
|
||||
|
||||
const handleChangeFolder = (folder, folderPath) => {
|
||||
setQueryObject((prev) => ({ ...prev, folder: folder ?? null, folderPath }));
|
||||
};
|
||||
|
||||
return [
|
||||
{ queryObject, rawQuery: stringify(queryObject, { encode: false }) },
|
||||
{
|
||||
onChangeFilters: handleChangeFilters,
|
||||
onChangeFolder: handleChangeFolder,
|
||||
onChangePage: handeChangePage,
|
||||
onChangePageSize: handleChangePageSize,
|
||||
onChangeSort: handleChangeSort,
|
||||
onChangeSearch: handleChangeSearch,
|
||||
},
|
||||
];
|
||||
};
|
||||
|
||||
export default useModalQueryParams;
|
||||
101
packages/core/upload/admin/src/hooks/useModalQueryParams.ts
Normal file
101
packages/core/upload/admin/src/hooks/useModalQueryParams.ts
Normal file
@ -0,0 +1,101 @@
|
||||
import * as React from 'react';
|
||||
|
||||
import { useTracking } from '@strapi/admin/strapi-admin';
|
||||
import { stringify } from 'qs';
|
||||
|
||||
import { useConfig } from './useConfig';
|
||||
import type { Query, FilterCondition } from '../../../shared/contracts/files';
|
||||
|
||||
const useModalQueryParams = (initialState?: Partial<Query>) => {
|
||||
const { trackUsage } = useTracking();
|
||||
const {
|
||||
config: { data: config },
|
||||
} = useConfig();
|
||||
|
||||
const [queryObject, setQueryObject] = React.useState<Query>({
|
||||
page: 1,
|
||||
sort: 'updatedAt:DESC',
|
||||
pageSize: 10,
|
||||
filters: {
|
||||
$and: [],
|
||||
},
|
||||
...initialState,
|
||||
});
|
||||
|
||||
React.useEffect(() => {
|
||||
if (config && 'sort' in config && 'pageSize' in config) {
|
||||
setQueryObject((prevQuery) => ({
|
||||
...prevQuery,
|
||||
sort: config.sort,
|
||||
pageSize: config.pageSize,
|
||||
}));
|
||||
}
|
||||
}, [config]);
|
||||
|
||||
const handleChangeFilters = (nextFilters: FilterCondition<string>[]) => {
|
||||
if (nextFilters) {
|
||||
trackUsage('didFilterMediaLibraryElements', {
|
||||
location: 'content-manager',
|
||||
filter: Object.keys(nextFilters[nextFilters.length - 1])[0],
|
||||
});
|
||||
setQueryObject((prev) => ({ ...prev, page: 1, filters: { $and: nextFilters } }));
|
||||
}
|
||||
};
|
||||
|
||||
const handleChangePageSize = (pageSize: Query['pageSize']) => {
|
||||
setQueryObject((prev) => ({
|
||||
...prev,
|
||||
pageSize: typeof pageSize === 'string' ? parseInt(pageSize, 10) : pageSize,
|
||||
page: 1,
|
||||
}));
|
||||
};
|
||||
|
||||
const handeChangePage = (page: Query['page']) => {
|
||||
setQueryObject((prev) => ({ ...prev, page }));
|
||||
};
|
||||
|
||||
const handleChangeSort = (sort: Query['sort']) => {
|
||||
if (sort) {
|
||||
trackUsage('didSortMediaLibraryElements', {
|
||||
location: 'content-manager',
|
||||
sort,
|
||||
});
|
||||
setQueryObject((prev) => ({ ...prev, sort }));
|
||||
}
|
||||
};
|
||||
|
||||
const handleChangeSearch = (_q: Query['_q']) => {
|
||||
if (_q) {
|
||||
setQueryObject((prev) => ({ ...prev, _q, page: 1 }));
|
||||
} else {
|
||||
const newState: Query = { page: 1 };
|
||||
|
||||
Object.keys(queryObject).forEach((key) => {
|
||||
if (!['page', '_q'].includes(key)) {
|
||||
// @ts-ignore
|
||||
newState[key] = queryObject[key];
|
||||
}
|
||||
});
|
||||
|
||||
setQueryObject(newState);
|
||||
}
|
||||
};
|
||||
|
||||
const handleChangeFolder = (folder: Query['folder'], folderPath: Query['folderPath']) => {
|
||||
setQueryObject((prev) => ({ ...prev, folder: folder ?? null, folderPath }));
|
||||
};
|
||||
|
||||
return [
|
||||
{ queryObject, rawQuery: stringify(queryObject, { encode: false }) },
|
||||
{
|
||||
onChangeFilters: handleChangeFilters,
|
||||
onChangeFolder: handleChangeFolder,
|
||||
onChangePage: handeChangePage,
|
||||
onChangePageSize: handleChangePageSize,
|
||||
onChangeSort: handleChangeSort,
|
||||
onChangeSearch: handleChangeSearch,
|
||||
},
|
||||
];
|
||||
};
|
||||
|
||||
export default useModalQueryParams;
|
||||
@ -1,38 +0,0 @@
|
||||
import { useNotification, useFetchClient } from '@strapi/admin/strapi-admin';
|
||||
import { useIntl } from 'react-intl';
|
||||
import { useMutation, useQueryClient } from 'react-query';
|
||||
|
||||
import pluginId from '../pluginId';
|
||||
|
||||
export const useRemoveAsset = (onSuccess) => {
|
||||
const { toggleNotification } = useNotification();
|
||||
const { formatMessage } = useIntl();
|
||||
const queryClient = useQueryClient();
|
||||
const { del } = useFetchClient();
|
||||
|
||||
const mutation = useMutation((assetId) => del(`/upload/files/${assetId}`), {
|
||||
onSuccess() {
|
||||
queryClient.refetchQueries([pluginId, 'assets'], { active: true });
|
||||
queryClient.refetchQueries([pluginId, 'asset-count'], { active: true });
|
||||
|
||||
toggleNotification({
|
||||
type: 'success',
|
||||
message: formatMessage({
|
||||
id: 'modal.remove.success-label',
|
||||
defaultMessage: 'Elements have been successfully deleted.',
|
||||
}),
|
||||
});
|
||||
|
||||
onSuccess();
|
||||
},
|
||||
onError(error) {
|
||||
toggleNotification({ type: 'danger', message: error.message });
|
||||
},
|
||||
});
|
||||
|
||||
const removeAsset = async (assetId) => {
|
||||
await mutation.mutateAsync(assetId);
|
||||
};
|
||||
|
||||
return { ...mutation, removeAsset };
|
||||
};
|
||||
42
packages/core/upload/admin/src/hooks/useRemoveAsset.ts
Normal file
42
packages/core/upload/admin/src/hooks/useRemoveAsset.ts
Normal file
@ -0,0 +1,42 @@
|
||||
import { useNotification, useFetchClient } from '@strapi/admin/strapi-admin';
|
||||
import { useIntl } from 'react-intl';
|
||||
import { useMutation, useQueryClient } from 'react-query';
|
||||
import type { DeleteFile } from '../../../shared/contracts/files';
|
||||
|
||||
import pluginId from '../pluginId';
|
||||
|
||||
export const useRemoveAsset = (onSuccess: () => void) => {
|
||||
const { toggleNotification } = useNotification();
|
||||
const { formatMessage } = useIntl();
|
||||
const queryClient = useQueryClient();
|
||||
const { del } = useFetchClient();
|
||||
|
||||
const mutation = useMutation(
|
||||
(assetId: number) => del<DeleteFile.Response>(`/upload/files/${assetId}`),
|
||||
{
|
||||
onSuccess() {
|
||||
queryClient.refetchQueries([pluginId, 'assets'], { active: true });
|
||||
queryClient.refetchQueries([pluginId, 'asset-count'], { active: true });
|
||||
|
||||
toggleNotification({
|
||||
type: 'success',
|
||||
message: formatMessage({
|
||||
id: 'modal.remove.success-label',
|
||||
defaultMessage: 'Elements have been successfully deleted.',
|
||||
}),
|
||||
});
|
||||
|
||||
onSuccess();
|
||||
},
|
||||
onError(error: Error) {
|
||||
toggleNotification({ type: 'danger', message: error.message });
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
const removeAsset = async (assetId: number) => {
|
||||
await mutation.mutateAsync(assetId);
|
||||
};
|
||||
|
||||
return { ...mutation, removeAsset };
|
||||
};
|
||||
@ -1,13 +1,24 @@
|
||||
import { useState } from 'react';
|
||||
import * as React from 'react';
|
||||
|
||||
import { useFetchClient } from '@strapi/admin/strapi-admin';
|
||||
import { useFetchClient, FetchClient } from '@strapi/admin/strapi-admin';
|
||||
import { useMutation, useQueryClient } from 'react-query';
|
||||
import { File, RawFile, CreateFile } from '../../../shared/contracts/files';
|
||||
|
||||
import pluginId from '../pluginId';
|
||||
|
||||
const endpoint = `/${pluginId}`;
|
||||
|
||||
const uploadAsset = (asset, folderId, signal, onProgress, post) => {
|
||||
interface Asset extends File {
|
||||
rawFile: RawFile;
|
||||
}
|
||||
|
||||
const uploadAsset = (
|
||||
asset: Asset,
|
||||
folderId: number,
|
||||
signal: AbortSignal,
|
||||
onProgress: (progress: number) => void,
|
||||
post: FetchClient['post']
|
||||
) => {
|
||||
const { rawFile, caption, name, alternativeText } = asset;
|
||||
const formData = new FormData();
|
||||
|
||||
@ -34,13 +45,17 @@ const uploadAsset = (asset, folderId, signal, onProgress, post) => {
|
||||
};
|
||||
|
||||
export const useUpload = () => {
|
||||
const [progress, setProgress] = useState(0);
|
||||
const [progress, setProgress] = React.useState(0);
|
||||
const queryClient = useQueryClient();
|
||||
const abortController = new AbortController();
|
||||
const signal = abortController.signal;
|
||||
const { post } = useFetchClient();
|
||||
|
||||
const mutation = useMutation(
|
||||
const mutation = useMutation<
|
||||
CreateFile.Response['data'],
|
||||
CreateFile.Response['error'],
|
||||
{ asset: Asset; folderId: number }
|
||||
>(
|
||||
({ asset, folderId }) => {
|
||||
return uploadAsset(asset, folderId, signal, setProgress, post);
|
||||
},
|
||||
@ -52,7 +67,7 @@ export const useUpload = () => {
|
||||
}
|
||||
);
|
||||
|
||||
const upload = (asset, folderId) => mutation.mutateAsync({ asset, folderId });
|
||||
const upload = (asset: Asset, folderId: number) => mutation.mutateAsync({ asset, folderId });
|
||||
|
||||
const cancel = () => abortController.abort();
|
||||
|
||||
@ -1,9 +0,0 @@
|
||||
export const recursiveRenameKeys = (obj, fn) =>
|
||||
Object.fromEntries(
|
||||
Object.entries(obj).map(([key, value]) => {
|
||||
const getValue = (v) =>
|
||||
typeof v === 'object' && v !== null ? recursiveRenameKeys(v, fn) : v;
|
||||
|
||||
return [fn(key), Array.isArray(value) ? value.map((val) => getValue(val)) : getValue(value)];
|
||||
})
|
||||
);
|
||||
18
packages/core/upload/admin/src/hooks/utils/rename-keys.ts
Normal file
18
packages/core/upload/admin/src/hooks/utils/rename-keys.ts
Normal file
@ -0,0 +1,18 @@
|
||||
type Primitive = string | number | boolean | null | undefined;
|
||||
|
||||
export type DeepRecord<T> = {
|
||||
[K in keyof T]: T[K] extends Primitive ? T[K] : DeepRecord<T[K]>;
|
||||
};
|
||||
|
||||
export const recursiveRenameKeys = <T extends object>(
|
||||
obj: T,
|
||||
fn: (key: string) => string
|
||||
): DeepRecord<T> =>
|
||||
Object.fromEntries(
|
||||
Object.entries(obj).map(([key, value]) => {
|
||||
const getValue = (v: unknown): any =>
|
||||
typeof v === 'object' && v !== null ? recursiveRenameKeys(v, fn) : v;
|
||||
|
||||
return [fn(key), Array.isArray(value) ? value.map((val) => getValue(val)) : getValue(value)];
|
||||
})
|
||||
) as DeepRecord<T>;
|
||||
@ -10,3 +10,43 @@ export enum AssetSource {
|
||||
Url = 'url',
|
||||
Computer = 'computer',
|
||||
}
|
||||
|
||||
export const PERMISSIONS = {
|
||||
// This permission regards the main component (App) and is used to tell
|
||||
// If the plugin link should be displayed in the menu
|
||||
// And also if the plugin is accessible. This use case is found when a user types the url of the
|
||||
// plugin directly in the browser
|
||||
main: [
|
||||
{ action: 'plugin::upload.read', subject: null },
|
||||
{
|
||||
action: 'plugin::upload.assets.create',
|
||||
subject: null,
|
||||
},
|
||||
{
|
||||
action: 'plugin::upload.assets.update',
|
||||
subject: null,
|
||||
},
|
||||
],
|
||||
copyLink: [
|
||||
{
|
||||
action: 'plugin::upload.assets.copy-link',
|
||||
subject: null,
|
||||
},
|
||||
],
|
||||
create: [
|
||||
{
|
||||
action: 'plugin::upload.assets.create',
|
||||
subject: null,
|
||||
},
|
||||
],
|
||||
download: [
|
||||
{
|
||||
action: 'plugin::upload.assets.download',
|
||||
subject: null,
|
||||
},
|
||||
],
|
||||
read: [{ action: 'plugin::upload.read', subject: null }],
|
||||
configureView: [{ action: 'plugin::upload.configure-view', subject: null }],
|
||||
settings: [{ action: 'plugin::upload.settings.read', subject: null }],
|
||||
update: [{ action: 'plugin::upload.assets.update', subject: null, fields: null }],
|
||||
};
|
||||
|
||||
@ -2,7 +2,7 @@ import { errors } from '@strapi/utils';
|
||||
|
||||
type SortOrder = 'ASC' | 'DESC';
|
||||
|
||||
type SortKey = 'createdAt' | 'name';
|
||||
type SortKey = 'createdAt' | 'name' | 'updatedAt';
|
||||
|
||||
// Abstract type for comparison operators where the keys are generic strings
|
||||
type ComparisonOperators<T> = {
|
||||
|
||||
@ -98,7 +98,7 @@ export declare namespace UpdateFolder {
|
||||
*
|
||||
* Return the structure of a folder.
|
||||
*/
|
||||
export declare namespace FolderStructureNamespace {
|
||||
export declare namespace GetFolderStructure {
|
||||
export interface Request {
|
||||
query?: {};
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user