diff --git a/ghost/admin-x-settings/src/components/settings/site/ThemeModal.tsx b/ghost/admin-x-settings/src/components/settings/site/ThemeModal.tsx index 0c62d9863e..c85ebd6cd9 100644 --- a/ghost/admin-x-settings/src/components/settings/site/ThemeModal.tsx +++ b/ghost/admin-x-settings/src/components/settings/site/ThemeModal.tsx @@ -4,38 +4,43 @@ import ButtonGroup from '../../../admin-x-ds/global/ButtonGroup'; import FileUpload from '../../../admin-x-ds/global/FileUpload'; import Modal from '../../../admin-x-ds/global/Modal'; import NewThemePreview from './theme/ThemePreview'; -import NiceModal, {useModal} from '@ebay/nice-modal-react'; +import NiceModal, {NiceModalHandler, useModal} from '@ebay/nice-modal-react'; import OfficialThemes from './theme/OfficialThemes'; +import React, {useState} from 'react'; import TabView from '../../../admin-x-ds/global/TabView'; -import {useState} from 'react'; +import {Theme} from '../../../types/api'; +import {showToast} from '../../../admin-x-ds/global/Toast'; +import {useApi} from '../../providers/ServiceProvider'; +import {useThemes} from '../../../hooks/useThemes'; -const ChangeThemeModal = NiceModal.create(() => { - const [currentTab, setCurrentTab] = useState('official'); - const [selectedTheme, setSelectedTheme] = useState(''); +interface ThemeToolbarProps { + selectedTheme: string; + setCurrentTab: (tab: string) => void; + setSelectedTheme: (theme: string) => void; + modal: NiceModalHandler>; + themes: Theme[]; + setThemes: (themes: Theme[]) => void; +} - const modal = useModal(); +interface ThemeModalContentProps { + selectedTheme: string; + onSelectTheme: (theme: string) => void; + currentTab: string; + themes: Theme[]; + setThemes: (themes: Theme[]) => void; +} - const onSelectTheme = (theme: string) => { - setSelectedTheme(theme); - }; - - let content; - switch (currentTab) { - case 'official': - if (selectedTheme) { - content = ; - } else { - content = ; - } - break; - case 'installed': - content = ; - break; - } - - let toolBar; +const ThemeToolbar: React.FC = ({ + selectedTheme, + setCurrentTab, + setSelectedTheme, + modal, + themes, + setThemes +}) => { + const api = useApi(); if (selectedTheme) { - toolBar = + return (
-
; + + ); } else { - toolBar = + return (
{ />
- { - alert(file.name); + { + const data = await api.themes.upload({file}); + const uploadedTheme = data.themes[0]; + setThemes([...themes, uploadedTheme]); + showToast({ + message: `Theme uploaded - ${uploadedTheme.name}` + }); }}>Upload theme
-
; + + ); } +}; + +const ThemeModalContent: React.FC = ({ + currentTab, + selectedTheme, + onSelectTheme, + themes, + setThemes +}) => { + switch (currentTab) { + case 'official': + if (selectedTheme) { + return ( + + ); + } else { + return ( + + ); + } + case 'installed': + return ( + + ); + } + return null; +}; + +const ChangeThemeModal = NiceModal.create(() => { + const [currentTab, setCurrentTab] = useState('official'); + const [selectedTheme, setSelectedTheme] = useState(''); + + const modal = useModal(); + const {themes, setThemes} = useThemes(); + + const onSelectTheme = (theme: string) => { + setSelectedTheme(theme); + }; return ( { >
- {toolBar} - {content} + +
diff --git a/ghost/admin-x-settings/src/components/settings/site/theme/AdvancedThemeSettings.tsx b/ghost/admin-x-settings/src/components/settings/site/theme/AdvancedThemeSettings.tsx index 9b72b822c2..1cb7674f28 100644 --- a/ghost/admin-x-settings/src/components/settings/site/theme/AdvancedThemeSettings.tsx +++ b/ghost/admin-x-settings/src/components/settings/site/theme/AdvancedThemeSettings.tsx @@ -1,12 +1,165 @@ +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 React from 'react'; +import {Theme} from '../../../../types/api'; +import {downloadFile, getGhostPaths} from '../../../../utils/helpers'; +import {isActiveTheme, isDefaultTheme, isDeletableTheme} from '../../../../models/themes'; +import {useApi} from '../../../providers/ServiceProvider'; -const AdvancedThemeSettings: React.FC = () => { +interface ThemeActionProps { + theme: Theme; + themes: Theme[]; + updateThemes: (themes: Theme[]) => void; +} + +interface ThemeSettingProps { + themes: Theme[]; + setThemes: (themes: Theme[]) => void; +} + +function getThemeLabel(theme: Theme): string { + let label = theme.package?.name || theme.name; + + if (isDefaultTheme(theme)) { + label += ' (default)'; + } else { + label += ` (${theme.name})`; + } + + if (isActiveTheme(theme)) { + label += ' (active)'; + } + + return label; +} + +function getThemeVersion(theme: Theme): string { + return theme.package?.version || '1.0'; +} + +const ThemeActions: React.FC = ({ + theme, + themes, + updateThemes +}) => { + const api = useApi(); + + const handleActivate = async () => { + const data = await api.themes.activate(theme.name); + const updatedTheme = data.themes[0]; + + const updatedThemes: Theme[] = themes.map((t) => { + if (t.name === updatedTheme.name) { + return updatedTheme; + } + return { + ...t, + active: false + }; + }); + updateThemes(updatedThemes); + }; + + const handleDelete = async () => { + await api.themes.delete(theme.name); + const updatedThemes = themes.filter(t => t.name !== theme.name); + updateThemes(updatedThemes); + }; + + const handleDownload = async () => { + const {apiRoot} = getGhostPaths(); + downloadFile(`${apiRoot}/themes/${theme.name}/download`); + }; + + let actions = []; + if (isDeletableTheme(theme)) { + actions.push( +