2
1
Fork 0
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:
Jono M 2023-06-29 18:14:53 +12:00 committed by GitHub
parent d4027a4797
commit c03d3ff384
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 116 additions and 66 deletions

View file

@ -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);

View file

@ -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);

View file

@ -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>

View file

@ -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

View file

@ -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;
}
}