mirror of
https://github.com/TryGhost/Ghost.git
synced 2025-02-17 23:44:39 -05:00
Wired up Pintura integration to AdminX (#17759)
refs https://github.com/TryGhost/Product/issues/3729 - Wired up the Pintura Integration to AdminX. --- <!-- Leave the line below if you'd like GitHub Copilot to generate a summary from your commit --> <!-- copilot:summary --> ### <samp>🤖 Generated by Copilot at 4d33355</samp> This pull request adds the ability to customize the Pintura image editor with custom JS and CSS files in the advanced settings modal. It introduces a new `files.ts` file in the `api` folder, which provides a hook and a helper function for uploading and retrieving files. It also modifies the `PinturaModal.tsx` component to use these functions and display the settings.
This commit is contained in:
parent
ab36892799
commit
d9cee38a77
5 changed files with 208 additions and 5 deletions
20
apps/admin-x-settings/src/api/files.ts
Normal file
20
apps/admin-x-settings/src/api/files.ts
Normal file
|
@ -0,0 +1,20 @@
|
|||
import {createMutation} from '../utils/apiRequests';
|
||||
|
||||
export interface FilesResponseType {
|
||||
files: {
|
||||
url: string;
|
||||
ref: string | null;
|
||||
}[];
|
||||
}
|
||||
|
||||
export const useUploadFile = createMutation<FilesResponseType, {file: File}>({
|
||||
method: 'POST',
|
||||
path: () => '/files/upload/',
|
||||
body: ({file}) => {
|
||||
const formData = new FormData();
|
||||
formData.append('file', file);
|
||||
return formData;
|
||||
}
|
||||
});
|
||||
|
||||
export const getFileUrl = (response: FilesResponseType) => response.files[0].url;
|
|
@ -7,12 +7,73 @@ import Toggle from '../../../../admin-x-ds/global/form/Toggle';
|
|||
import pinturaScreenshot from '../../../../assets/images/pintura-screenshot.png';
|
||||
import useRouting from '../../../../hooks/useRouting';
|
||||
import {ReactComponent as Icon} from '../../../../assets/icons/pintura.svg';
|
||||
import {useState} from 'react';
|
||||
import {Setting, getSettingValues, useEditSettings} from '../../../../api/settings';
|
||||
import {showToast} from '../../../../admin-x-ds/global/Toast';
|
||||
import {useEffect, useRef, useState} from 'react';
|
||||
import {useGlobalData} from '../../../providers/GlobalDataProvider';
|
||||
import {useUploadFile} from '../../../../api/files';
|
||||
|
||||
const PinturaModal = NiceModal.create(() => {
|
||||
const {updateRoute} = useRouting();
|
||||
const modal = NiceModal.useModal();
|
||||
const [enabled, setEnabled] = useState(false);
|
||||
const [uploadingState, setUploadingState] = useState({
|
||||
js: false,
|
||||
css: false
|
||||
});
|
||||
|
||||
const {settings} = useGlobalData();
|
||||
const [pinturaEnabled] = getSettingValues<boolean>(settings, ['pintura']);
|
||||
const {mutateAsync: editSettings} = useEditSettings();
|
||||
const {mutateAsync: uploadFile} = useUploadFile();
|
||||
|
||||
useEffect(() => {
|
||||
setEnabled(pinturaEnabled || false);
|
||||
}, [pinturaEnabled]);
|
||||
|
||||
const jsUploadRef = useRef<HTMLInputElement>(null);
|
||||
const cssUploadRef = useRef<HTMLInputElement>(null);
|
||||
const triggerUpload = (form: string) => {
|
||||
if (form === 'js') {
|
||||
jsUploadRef.current?.click();
|
||||
}
|
||||
|
||||
if (form === 'css') {
|
||||
cssUploadRef.current?.click();
|
||||
}
|
||||
};
|
||||
|
||||
const handleUpload = async (event: React.ChangeEvent<HTMLInputElement>, form: 'js' | 'css') => {
|
||||
try {
|
||||
setUploadingState(prev => ({...prev, [form]: true}));
|
||||
|
||||
const file = event.target?.files?.[0];
|
||||
if (!file) {
|
||||
return;
|
||||
}
|
||||
|
||||
const {files} = await uploadFile({file});
|
||||
const url = files[0].url;
|
||||
const updates : Setting[] = [
|
||||
{key: `pintura_${form}_url`, value: url}
|
||||
];
|
||||
|
||||
await editSettings(updates);
|
||||
|
||||
setUploadingState(prev => ({...prev, [form]: false}));
|
||||
|
||||
showToast({
|
||||
type: 'success',
|
||||
message: `Pintura ${form} uploaded successfully`
|
||||
});
|
||||
} catch (e) {
|
||||
setUploadingState({js: false, css: false});
|
||||
showToast({
|
||||
type: 'pageError',
|
||||
message: `Can't upload Pintura ${form}!`
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal
|
||||
|
@ -22,9 +83,14 @@ const PinturaModal = NiceModal.create(() => {
|
|||
cancelLabel=''
|
||||
okColor='black'
|
||||
okLabel='Save'
|
||||
testId='pintura-modal'
|
||||
title=''
|
||||
onOk={() => {
|
||||
onOk={async () => {
|
||||
modal.remove();
|
||||
updateRoute('integrations');
|
||||
await editSettings([
|
||||
{key: 'pintura', value: enabled}
|
||||
]);
|
||||
}}
|
||||
>
|
||||
<IntegrationHeader
|
||||
|
@ -46,6 +112,7 @@ const PinturaModal = NiceModal.create(() => {
|
|||
</div>
|
||||
<Form marginBottom={false} title='Pintura configuration' grouped>
|
||||
<Toggle
|
||||
checked={enabled}
|
||||
direction='rtl'
|
||||
hint={<>Enable <a className='text-green' href="https://pqina.nl/pintura/?ref=ghost.org" rel="noopener noreferrer" target="_blank">Pintura</a> for editing your images in Ghost</>}
|
||||
label='Enable Pintura'
|
||||
|
@ -60,14 +127,24 @@ const PinturaModal = NiceModal.create(() => {
|
|||
<div>Upload Pintura script</div>
|
||||
<div className='text-xs text-grey-600'>Upload the <code>pintura-umd.js</code> file from the Pintura package</div>
|
||||
</div>
|
||||
<Button color='outline' label='Upload' />
|
||||
<input ref={jsUploadRef} accept='.js' type="file" hidden onChange={async (e) => {
|
||||
await handleUpload(e, 'js');
|
||||
}} />
|
||||
<Button color='outline' disabled={uploadingState.js} label='Upload' onClick={() => {
|
||||
triggerUpload('js');
|
||||
}} />
|
||||
</div>
|
||||
<div className='flex items-center justify-between'>
|
||||
<div>
|
||||
<div>Upload Pintura styles</div>
|
||||
<div className='text-xs text-grey-600'>Upload the <code>pintura.css</code> file from the Pintura package</div>
|
||||
</div>
|
||||
<Button color='outline' label='Upload' />
|
||||
<input ref={cssUploadRef} accept='.css' type="file" hidden onChange={async (e) => {
|
||||
await handleUpload(e, 'css');
|
||||
}} />
|
||||
<Button color='outline' disabled={uploadingState.css} label='Upload' onClick={() => {
|
||||
triggerUpload('css');
|
||||
}} />
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
@ -77,4 +154,4 @@ const PinturaModal = NiceModal.create(() => {
|
|||
);
|
||||
});
|
||||
|
||||
export default PinturaModal;
|
||||
export default PinturaModal;
|
||||
|
|
|
@ -0,0 +1,104 @@
|
|||
import {expect, test} from '@playwright/test';
|
||||
import {globalDataRequests, mockApi, updatedSettingsResponse} from '../../../utils/e2e';
|
||||
|
||||
test.describe('Pintura integration', async () => {
|
||||
test('Can toggle Pintura', async ({page}) => {
|
||||
const {lastApiRequests} = await mockApi({page, requests: {
|
||||
...globalDataRequests,
|
||||
editSettings: {method: 'PUT', path: '/settings/', response: updatedSettingsResponse([
|
||||
{key: 'pintura', value: false}
|
||||
])}
|
||||
}});
|
||||
|
||||
await page.goto('/');
|
||||
const section = page.getByTestId('integrations');
|
||||
const pinturaElement = section.getByText('Pintura').last();
|
||||
await pinturaElement.hover();
|
||||
await page.getByRole('button', {name: 'Configure'}).click();
|
||||
const pinturaModal = page.getByTestId('pintura-modal');
|
||||
const pinturaToggle = pinturaModal.getByRole('switch');
|
||||
await pinturaToggle.click();
|
||||
// Upload Pintura script text should be visible
|
||||
await expect(pinturaModal.getByText('Upload Pintura script')).toBeVisible();
|
||||
await expect(pinturaModal.getByText('Upload Pintura styles')).toBeVisible();
|
||||
await pinturaToggle.click();
|
||||
await expect(pinturaModal.getByText('Upload Pintura script')).not.toBeVisible();
|
||||
await expect(pinturaModal.getByText('Upload Pintura styles')).not.toBeVisible();
|
||||
|
||||
// we want it true, so click again
|
||||
await pinturaToggle.click();
|
||||
|
||||
await page.getByRole('button', {name: 'Save'}).click();
|
||||
|
||||
expect(lastApiRequests.editSettings?.body).toEqual({
|
||||
settings: [
|
||||
{key: 'pintura', value: true}
|
||||
]
|
||||
});
|
||||
});
|
||||
|
||||
test('Can upload Pintura script', async ({page}) => {
|
||||
const {lastApiRequests} = await mockApi({page, requests: {
|
||||
...globalDataRequests,
|
||||
uploadFile: {method: 'POST', path: '/files/upload/', response: {files: [{url: 'http://example.com/pintura-umd.js', ref: null}]}},
|
||||
editSettings: {method: 'PUT', path: '/settings/', response: updatedSettingsResponse([
|
||||
{key: 'pintura_js_url', value: 'http://example.com/pintura-umd.js'}
|
||||
])}
|
||||
}});
|
||||
|
||||
await page.goto('/');
|
||||
|
||||
const section = page.getByTestId('integrations');
|
||||
const pinturaElement = section.getByText('Pintura').last();
|
||||
await pinturaElement.hover();
|
||||
await page.getByRole('button', {name: 'Configure'}).click();
|
||||
const pinturaModal = page.getByTestId('pintura-modal');
|
||||
const pinturaToggle = pinturaModal.getByRole('switch');
|
||||
await pinturaToggle.click();
|
||||
const jsFileChooserPromise = page.waitForEvent('filechooser');
|
||||
|
||||
const jsUploadButton = pinturaModal.getByRole('button', {name: 'Upload'}).first();
|
||||
await jsUploadButton.click();
|
||||
const jsFileChooser = await jsFileChooserPromise;
|
||||
await jsFileChooser.setFiles(`${__dirname}/../../../utils/files/pintura-umd.js`);
|
||||
|
||||
await expect(jsUploadButton).toBeEnabled();
|
||||
expect(lastApiRequests.editSettings?.body).toEqual({
|
||||
settings: [
|
||||
{key: 'pintura_js_url', value: 'http://example.com/pintura-umd.js'}
|
||||
]
|
||||
});
|
||||
});
|
||||
|
||||
test('Can upload Pintura styles', async ({page}) => {
|
||||
const {lastApiRequests} = await mockApi({page, requests: {
|
||||
...globalDataRequests,
|
||||
uploadFile: {method: 'POST', path: '/files/upload/', response: {files: [{url: 'http://example.com/pintura.css', ref: null}]}},
|
||||
editSettings: {method: 'PUT', path: '/settings/', response: updatedSettingsResponse([
|
||||
{key: 'pintura_css_url', value: 'http://example.com/pintura.css'}
|
||||
])}
|
||||
}});
|
||||
|
||||
await page.goto('/');
|
||||
|
||||
const section = page.getByTestId('integrations');
|
||||
const pinturaElement = section.getByText('Pintura').last();
|
||||
await pinturaElement.hover();
|
||||
await page.getByRole('button', {name: 'Configure'}).click();
|
||||
const pinturaModal = page.getByTestId('pintura-modal');
|
||||
const pinturaToggle = pinturaModal.getByRole('switch');
|
||||
await pinturaToggle.click();
|
||||
const cssFileChooserPromise = page.waitForEvent('filechooser');
|
||||
|
||||
const cssUploadButton = pinturaModal.getByRole('button', {name: 'Upload'}).last();
|
||||
await cssUploadButton.click();
|
||||
const cssFileChooser = await cssFileChooserPromise;
|
||||
await cssFileChooser.setFiles(`${__dirname}/../../../utils/files/pintura.css`);
|
||||
await expect(cssUploadButton).toBeEnabled();
|
||||
expect(lastApiRequests.editSettings?.body).toEqual({
|
||||
settings: [
|
||||
{key: 'pintura_css_url', value: 'http://example.com/pintura.css'}
|
||||
]
|
||||
});
|
||||
});
|
||||
});
|
1
apps/admin-x-settings/test/utils/files/pintura-umd.js
Normal file
1
apps/admin-x-settings/test/utils/files/pintura-umd.js
Normal file
|
@ -0,0 +1 @@
|
|||
// This is a dummy file for e2e testing purposes
|
1
apps/admin-x-settings/test/utils/files/pintura.css
Normal file
1
apps/admin-x-settings/test/utils/files/pintura.css
Normal file
|
@ -0,0 +1 @@
|
|||
/* this is a dummy css file for e2e testing purposes */
|
Loading…
Add table
Reference in a new issue