mirror of
https://github.com/TryGhost/Ghost.git
synced 2023-12-13 21:00:40 +01:00
Factored out theme installed modal (#17158)
refs https://github.com/TryGhost/Team/issues/3349 Will be reused in future changes, just doing a quick refactor first to prevent conflicts
This commit is contained in:
parent
d4027a4797
commit
c03d3ff384
5 changed files with 116 additions and 66 deletions
|
@ -16,7 +16,7 @@ export interface ConfirmationModalProps {
|
|||
customFooter?: boolean | React.ReactNode;
|
||||
}
|
||||
|
||||
const ConfirmationModal: React.FC<ConfirmationModalProps> = ({
|
||||
export const ConfirmationModalContent: React.FC<ConfirmationModalProps> = ({
|
||||
title = 'Are you sure?',
|
||||
prompt,
|
||||
cancelLabel = 'Cancel',
|
||||
|
@ -53,4 +53,4 @@ const ConfirmationModal: React.FC<ConfirmationModalProps> = ({
|
|||
);
|
||||
};
|
||||
|
||||
export default NiceModal.create(ConfirmationModal);
|
||||
export default NiceModal.create(ConfirmationModalContent);
|
||||
|
|
|
@ -0,0 +1,94 @@
|
|||
import Button from '../../../admin-x-ds/global/Button';
|
||||
import Heading from '../../../admin-x-ds/global/Heading';
|
||||
import List from '../../../admin-x-ds/global/List';
|
||||
import ListItem from '../../../admin-x-ds/global/ListItem';
|
||||
import NiceModal from '@ebay/nice-modal-react';
|
||||
import React, {ReactNode, useState} from 'react';
|
||||
import {ConfirmationModalContent} from '../../../admin-x-ds/global/modal/ConfirmationModal';
|
||||
import {InstalledTheme, Theme, ThemeProblem} from '../../../types/api';
|
||||
import {useApi} from '../../providers/ServiceProvider';
|
||||
|
||||
const ThemeProblemView = ({problem}:{problem: ThemeProblem}) => {
|
||||
const [isExpanded, setExpanded] = useState(false);
|
||||
|
||||
return <ListItem
|
||||
action={<Button color="green" label={isExpanded ? 'Collapse' : 'Expand'} link onClick={() => setExpanded(!isExpanded)} />}
|
||||
detail={
|
||||
isExpanded ?
|
||||
<>
|
||||
<div dangerouslySetInnerHTML={{__html: problem.details}} />
|
||||
<Heading level={6}>Affected files:</Heading>
|
||||
<ul>
|
||||
{problem.failures.map(failure => <li><code>{failure.ref}</code>{failure.message ? `: ${failure.message}` : ''}</li>)}
|
||||
</ul>
|
||||
</> :
|
||||
null
|
||||
}
|
||||
title={<>
|
||||
<strong>{problem.level === 'error' ? 'Error: ' : 'Warning: '}</strong>
|
||||
<span dangerouslySetInnerHTML={{__html: problem.rule}} />
|
||||
</>}
|
||||
hideActions
|
||||
separator
|
||||
/>;
|
||||
};
|
||||
|
||||
const ThemeInstalledModal: React.FC<{
|
||||
title: string
|
||||
prompt: ReactNode
|
||||
installedTheme: InstalledTheme;
|
||||
setThemes: (callback: (themes: Theme[]) => Theme[]) => void;
|
||||
}> = ({title, prompt, installedTheme, setThemes}) => {
|
||||
const api = useApi();
|
||||
|
||||
let errorPrompt = null;
|
||||
if (installedTheme.errors) {
|
||||
errorPrompt = <div className="mt-6">
|
||||
<List hint={<>Highly recommended to fix, functionality <strong>could</strong> be restricted</>} title="Errors">
|
||||
{installedTheme.errors?.map(error => <ThemeProblemView problem={error} />)}
|
||||
</List>
|
||||
</div>;
|
||||
}
|
||||
|
||||
let warningPrompt = null;
|
||||
if (installedTheme.warnings) {
|
||||
warningPrompt = <div className="mt-6">
|
||||
<List title="Warnings">
|
||||
{installedTheme.warnings?.map(warning => <ThemeProblemView problem={warning} />)}
|
||||
</List>
|
||||
</div>;
|
||||
}
|
||||
|
||||
return <ConfirmationModalContent
|
||||
cancelLabel='Close'
|
||||
okColor='black'
|
||||
okLabel={`Activate${installedTheme.errors?.length ? ' with errors' : ''}`}
|
||||
okRunningLabel='Activating...'
|
||||
prompt={<>
|
||||
{prompt}
|
||||
{errorPrompt}
|
||||
{warningPrompt}
|
||||
</>}
|
||||
title={title}
|
||||
onOk={async (activateModal) => {
|
||||
const resData = await api.themes.activate(installedTheme.name);
|
||||
const updatedTheme = resData.themes[0];
|
||||
|
||||
setThemes((_themes) => {
|
||||
const updatedThemes: Theme[] = _themes.map((t) => {
|
||||
if (t.name === updatedTheme.name) {
|
||||
return updatedTheme;
|
||||
}
|
||||
return {
|
||||
...t,
|
||||
active: false
|
||||
};
|
||||
});
|
||||
return updatedThemes;
|
||||
});
|
||||
activateModal?.remove();
|
||||
}}
|
||||
/>;
|
||||
};
|
||||
|
||||
export default NiceModal.create(ThemeInstalledModal);
|
|
@ -2,19 +2,17 @@ import AdvancedThemeSettings from './theme/AdvancedThemeSettings';
|
|||
import Button from '../../../admin-x-ds/global/Button';
|
||||
import ConfirmationModal from '../../../admin-x-ds/global/modal/ConfirmationModal';
|
||||
import FileUpload from '../../../admin-x-ds/global/form/FileUpload';
|
||||
import Heading from '../../../admin-x-ds/global/Heading';
|
||||
import List from '../../../admin-x-ds/global/List';
|
||||
import ListItem from '../../../admin-x-ds/global/ListItem';
|
||||
import Modal from '../../../admin-x-ds/global/modal/Modal';
|
||||
import NiceModal, {NiceModalHandler, useModal} from '@ebay/nice-modal-react';
|
||||
import OfficialThemes from './theme/OfficialThemes';
|
||||
import PageHeader from '../../../admin-x-ds/global/layout/PageHeader';
|
||||
import React, {useState} from 'react';
|
||||
import TabView from '../../../admin-x-ds/global/TabView';
|
||||
import ThemeInstalledModal from './ThemeInstalledModal';
|
||||
import ThemePreview from './theme/ThemePreview';
|
||||
import {API} from '../../../utils/api';
|
||||
import {OfficialTheme} from '../../../models/themes';
|
||||
import {Theme, ThemeProblem} from '../../../types/api';
|
||||
import {Theme} from '../../../types/api';
|
||||
import {showToast} from '../../../admin-x-ds/global/Toast';
|
||||
import {useApi} from '../../providers/ServiceProvider';
|
||||
import {useThemes} from '../../../hooks/useThemes';
|
||||
|
@ -51,31 +49,6 @@ function addThemeToList(theme: Theme, themes: Theme[]): Theme[] {
|
|||
return [...themes, theme];
|
||||
}
|
||||
|
||||
const ThemeProblemView = ({problem}:{problem: ThemeProblem}) => {
|
||||
const [isExpanded, setExpanded] = useState(false);
|
||||
|
||||
return <ListItem
|
||||
action={<Button color="green" label={isExpanded ? 'Collapse' : 'Expand'} link onClick={() => setExpanded(!isExpanded)} />}
|
||||
detail={
|
||||
isExpanded ?
|
||||
<>
|
||||
<div dangerouslySetInnerHTML={{__html: problem.details}} />
|
||||
<Heading level={6}>Affected files:</Heading>
|
||||
<ul>
|
||||
{problem.failures.map(failure => <li><code>{failure.ref}</code>{failure.message ? `: ${failure.message}` : ''}</li>)}
|
||||
</ul>
|
||||
</> :
|
||||
null
|
||||
}
|
||||
title={<>
|
||||
<strong>{problem.level === 'error' ? 'Error: ' : 'Warning: '}</strong>
|
||||
<span dangerouslySetInnerHTML={{__html: problem.rule}} />
|
||||
</>}
|
||||
hideActions
|
||||
separator
|
||||
/>;
|
||||
};
|
||||
|
||||
async function handleThemeUpload({
|
||||
api,
|
||||
file,
|
||||
|
@ -105,39 +78,14 @@ async function handleThemeUpload({
|
|||
prompt = <>
|
||||
The theme <strong>"{uploadedTheme.name}"</strong> was installed successfully but we detected some {hasErrors ? 'errors' : 'warnings'}.
|
||||
You are still able to activate and use the theme but it is recommended to fix these {hasErrors ? 'errors' : 'warnings'} before you do so.
|
||||
|
||||
<List>
|
||||
{uploadedTheme.errors?.map(error => <ThemeProblemView problem={error} />)}
|
||||
{uploadedTheme.warnings?.map(warning => <ThemeProblemView problem={warning} />)}
|
||||
</List>
|
||||
</>;
|
||||
}
|
||||
|
||||
NiceModal.show(ConfirmationModal, {
|
||||
NiceModal.show(ThemeInstalledModal, {
|
||||
title,
|
||||
prompt,
|
||||
okLabel: `Activate${uploadedTheme.errors?.length ? ' with errors' : ''}`,
|
||||
cancelLabel: 'Close',
|
||||
okRunningLabel: 'Activating...',
|
||||
okColor: 'black',
|
||||
onOk: async (activateModal) => {
|
||||
const resData = await api.themes.activate(uploadedTheme.name);
|
||||
const updatedTheme = resData.themes[0];
|
||||
|
||||
setThemes((_themes) => {
|
||||
const updatedThemes: Theme[] = _themes.map((t) => {
|
||||
if (t.name === updatedTheme.name) {
|
||||
return updatedTheme;
|
||||
}
|
||||
return {
|
||||
...t,
|
||||
active: false
|
||||
};
|
||||
});
|
||||
return updatedThemes;
|
||||
});
|
||||
activateModal?.remove();
|
||||
}
|
||||
installedTheme: uploadedTheme,
|
||||
setThemes
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -179,11 +127,14 @@ const ThemeToolbar: React.FC<ThemeToolbarProps> = ({
|
|||
cancelLabel: 'Cancel',
|
||||
okRunningLabel: 'Overwriting...',
|
||||
okColor: 'red',
|
||||
onOk: async (_modal) => {
|
||||
onOk: async (confirmModal) => {
|
||||
confirmModal?.remove();
|
||||
setCurrentTab('installed');
|
||||
handleThemeUpload({api, file, setThemes});
|
||||
}
|
||||
});
|
||||
} else {
|
||||
setCurrentTab('installed');
|
||||
handleThemeUpload({api, file, setThemes});
|
||||
}
|
||||
}}>Upload theme</FileUpload>
|
||||
|
|
|
@ -139,6 +139,11 @@ export type Theme = {
|
|||
templates?: string[];
|
||||
}
|
||||
|
||||
export type InstalledTheme = Theme & {
|
||||
errors?: ThemeProblem<'error'>[];
|
||||
warnings?: ThemeProblem<'warning'>[];
|
||||
}
|
||||
|
||||
export type ThemeProblem<Level extends string = 'error' | 'warning'> = {
|
||||
code: string
|
||||
details: string
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import {CustomThemeSetting, Label, Offer, Post, Setting, SiteData, Theme, ThemeProblem, Tier, User, UserRole} from '../types/api';
|
||||
import {CustomThemeSetting, InstalledTheme, Label, Offer, Post, Setting, SiteData, Theme, Tier, User, UserRole} from '../types/api';
|
||||
import {getGhostPaths} from './helpers';
|
||||
|
||||
interface Meta {
|
||||
|
@ -94,8 +94,8 @@ export interface ThemesResponseType {
|
|||
themes: Theme[];
|
||||
}
|
||||
|
||||
export interface ThemesUploadResponseType {
|
||||
themes: Array<Theme & { errors?: ThemeProblem<'error'>[], warnings?: ThemeProblem<'warning'>[] }>;
|
||||
export interface ThemesInstallResponseType {
|
||||
themes: InstalledTheme[];
|
||||
}
|
||||
|
||||
interface RequestOptions {
|
||||
|
@ -172,8 +172,8 @@ export interface API {
|
|||
browse: () => Promise<ThemesResponseType>;
|
||||
activate: (themeName: string) => Promise<ThemesResponseType>;
|
||||
delete: (themeName: string) => Promise<void>;
|
||||
install: (repo: string) => Promise<ThemesResponseType>;
|
||||
upload: ({file}: {file: File}) => Promise<ThemesUploadResponseType>;
|
||||
install: (repo: string) => Promise<ThemesInstallResponseType>;
|
||||
upload: ({file}: {file: File}) => Promise<ThemesInstallResponseType>;
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -437,7 +437,7 @@ function setupGhostApi({ghostVersion}: GhostApiOptions): API {
|
|||
body: formData,
|
||||
headers: {}
|
||||
});
|
||||
const data: ThemesUploadResponseType = await response.json();
|
||||
const data: ThemesInstallResponseType = await response.json();
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue