mirror of
https://github.com/TryGhost/Ghost.git
synced 2025-02-24 23:48:13 -05:00
Added install+activate process to view theme screen
closes https://github.com/TryGhost/Team/issues/1130 - migrated install theme process to new modal system and changed to install+activate - added "Use theme" button that opens the new install+activate modal - when the view theme screen opens the install modal, an `onSuccess` callback is passed which sets a property that will skip closing the install modal when the view theme modal is closed and transitions to the `settings.design` route leaving the "success" modal state on screen - added a `skipErrors` option to `themeManage.activateThemeTask` so that it can be used from processes that already handle theme errors without opening extra modals on top
This commit is contained in:
parent
342d9a242c
commit
569e4576d5
6 changed files with 312 additions and 32 deletions
115
ghost/admin/app/components/modals/design/install-theme.hbs
Normal file
115
ghost/admin/app/components/modals/design/install-theme.hbs
Normal file
|
@ -0,0 +1,115 @@
|
|||
<div class="modal-content">
|
||||
<div class="theme-validation-container">
|
||||
<header class="modal-header" data-test-modal="upload-theme">
|
||||
<h1>{{if this.installSuccess "Theme installed and activated" "Use this theme"}}</h1>
|
||||
</header>
|
||||
<button type="button" class="close" title="Close" {{on "click" @close}}>{{svg-jar "close"}}<span class="hidden">Close</span></button>
|
||||
|
||||
<div class="modal-body">
|
||||
{{#if this.isConfirming}}
|
||||
<p>
|
||||
You're about to use the <strong>{{this.themeName}}</strong> theme for your site.
|
||||
{{#unless this.willOverwriteExisting}}The look of your site will instantly be updated.{{/unless}}
|
||||
</p>
|
||||
|
||||
{{#if this.willOverwriteExisting}}
|
||||
<p>
|
||||
You already have a version of <strong>{{this.themeName}}</strong> installed.
|
||||
This will overwrite your existing version, any custom changes to theme files will be lost.
|
||||
</p>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
|
||||
{{#if this.willOverwriteDefault}}
|
||||
<p>
|
||||
Sorry, the default Casper theme cannot be overwritten.<br>
|
||||
If you wish to make changes please download the theme and upload a renamed zip file.
|
||||
</p>
|
||||
{{/if}}
|
||||
|
||||
{{#if this.installSuccess}}
|
||||
{{#if this.hasWarningsOrErrors}}
|
||||
<p>
|
||||
The theme <strong>"{{this.themeName}}"</strong> was installed successfully but we detected some {{if this.validationErrors "errors" "warnings"}}.
|
||||
</p>
|
||||
{{else}}
|
||||
{{!-- Installed with no errors --}}
|
||||
<p>The theme <strong>"{{this.themeName}}"</strong> was installed successfully.</p>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
|
||||
{{#if this.installError}}
|
||||
{{!-- Outright failure - not found, not a theme, server error, etc --}}
|
||||
<p>{{this.themeName}} failed to install.</p>
|
||||
<p class="error"><strong class="response">{{this.installError}}</strong></p>
|
||||
{{/if}}
|
||||
|
||||
{{#if this.installFailure}}
|
||||
{{!-- Invalid theme --}}
|
||||
<p>This theme is invalid and cannot be installed. Contact the theme developer.</p>
|
||||
{{/if}}
|
||||
|
||||
{{#if this.fatalValidationErrors}}
|
||||
<div>
|
||||
<h2 class="mb0 mt4 f5 fw6">Fatal Errors</h2>
|
||||
<p class="mb2">Must-fix to install theme</p>
|
||||
</div>
|
||||
|
||||
<ul class="pa0">
|
||||
{{#each this.fatalValidationErrors as |error|}}
|
||||
<li class="theme-validation-item theme-fatal-error">
|
||||
<GhThemeErrorLi @error={{error}} />
|
||||
</li>
|
||||
{{/each}}
|
||||
</ul>
|
||||
{{/if}}
|
||||
|
||||
{{#if this.validationErrors}}
|
||||
<div>
|
||||
<h2 class="mb0 mt4 f5 fw6">Errors</h2>
|
||||
<p class="mb2">Highly recommended to fix, functionality <strong>could</strong> be restricted</p>
|
||||
</div>
|
||||
<ul class="pa0">
|
||||
{{#each this.validationErrors as |error|}}
|
||||
<li class="theme-validation-item theme-error">
|
||||
<GhThemeErrorLi @error={{error}} />
|
||||
</li>
|
||||
{{/each}}
|
||||
</ul>
|
||||
{{/if}}
|
||||
|
||||
{{#if this.validationWarnings}}
|
||||
<div>
|
||||
<h2 class="mb0 mt4 f5 fw6">Warnings</h2>
|
||||
</div>
|
||||
<ul class="pa0">
|
||||
{{#each this.validationWarnings as |error|}}
|
||||
<li class="theme-validation-item theme-warning">
|
||||
<GhThemeErrorLi @error={{error}} />
|
||||
</li>
|
||||
{{/each}}
|
||||
</ul>
|
||||
{{/if}}
|
||||
</div>
|
||||
|
||||
<div class="modal-footer">
|
||||
<div class="flex items-center justify-between">
|
||||
<button {{on "click" (fn @close false)}} class="gh-btn" data-test-button="cancel">
|
||||
<span>{{if (or this.installSuccess this.installFailure) "Close" "Cancel"}}</span>
|
||||
</button>
|
||||
|
||||
{{#if this.shouldShowInstall}}
|
||||
<GhTaskButton
|
||||
@disabled={{this.refreshThemesTask.isRunning}}
|
||||
@buttonText={{if this.willOverwriteExisting "Overwrite" "Use theme"}}
|
||||
@runningText="Installing"
|
||||
@successText="Installed"
|
||||
@task={{this.installThemeTask}}
|
||||
@unlinkedTask={{true}} {{!-- button will be removed on success so avoid self-cancel warning --}}
|
||||
@class="gh-btn gh-btn-primary gh-btn-icon"
|
||||
/>
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
124
ghost/admin/app/components/modals/design/install-theme.js
Normal file
124
ghost/admin/app/components/modals/design/install-theme.js
Normal file
|
@ -0,0 +1,124 @@
|
|||
import Component from '@glimmer/component';
|
||||
import {isThemeValidationError} from 'ghost-admin/services/ajax';
|
||||
import {inject as service} from '@ember/service';
|
||||
import {task} from 'ember-concurrency-decorators';
|
||||
import {tracked} from '@glimmer/tracking';
|
||||
|
||||
export default class InstallThemeModalComponent extends Component {
|
||||
@service ajax;
|
||||
@service ghostPaths;
|
||||
@service store;
|
||||
@service themeManagement;
|
||||
|
||||
@tracked installedTheme = null;
|
||||
@tracked installError = '';
|
||||
@tracked validationWarnings = [];
|
||||
@tracked validationErrors = [];
|
||||
@tracked fatalValidationErrors = [];
|
||||
|
||||
themes = this.store.peekAll('theme');
|
||||
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
this.refreshThemesTask.perform();
|
||||
}
|
||||
|
||||
get themeName() {
|
||||
return this.args.data.theme.name;
|
||||
}
|
||||
|
||||
get isConfirming() {
|
||||
return !this.installSuccess && !this.installError && !this.installFailure && !this.willOverwriteDefault;
|
||||
}
|
||||
|
||||
get installSuccess() {
|
||||
return !!this.installedTheme;
|
||||
}
|
||||
|
||||
get installFailure() {
|
||||
return !this.installSuccess && (this.validationErrors.length || this.fatalValidationErrors.length);
|
||||
}
|
||||
|
||||
get willOverwriteDefault() {
|
||||
return this.themeName.toLowerCase() === 'casper';
|
||||
}
|
||||
|
||||
get willOverwriteExisting() {
|
||||
return this.themes.findBy('name', this.themeName.toLowerCase());
|
||||
}
|
||||
|
||||
get hasWarningsOrErrors() {
|
||||
return this.validationWarnings.length > 0 || this.validationErrors.length > 0;
|
||||
}
|
||||
|
||||
get shouldShowInstall() {
|
||||
return !this.installSuccess && !this.installFailure && !this.willOverwriteDefault;
|
||||
}
|
||||
|
||||
@task
|
||||
*refreshThemesTask() {
|
||||
yield this.store.findAll('theme', {reload: true});
|
||||
}
|
||||
|
||||
@task
|
||||
*installThemeTask() {
|
||||
try {
|
||||
const url = this.ghostPaths.url.api('themes/install') + `?source=github&ref=${this.args.data.theme.ref}`;
|
||||
const result = yield this.ajax.post(url);
|
||||
|
||||
this.installError = '';
|
||||
|
||||
if (result.themes) {
|
||||
// show theme in list immediately
|
||||
this.store.pushPayload(result);
|
||||
|
||||
this.installedTheme = this.store.peekRecord('theme', result.themes[0].name);
|
||||
|
||||
this.validationWarnings = this.installedTheme.warnings || [];
|
||||
this.validationErrors = this.installedTheme.errors || [];
|
||||
this.fatalValidationErrors = [];
|
||||
|
||||
// activate but prevent additional error modal from showing
|
||||
yield this.themeManagement.activateTask.perform(this.installedTheme, {skipErrors: true});
|
||||
|
||||
// let modal opener do any other background stuff
|
||||
this.args.data.onSuccess?.();
|
||||
|
||||
return true;
|
||||
}
|
||||
} catch (error) {
|
||||
if (isThemeValidationError(error)) {
|
||||
this.resetErrors();
|
||||
|
||||
let errors = error.payload.errors[0].details.errors;
|
||||
let fatalErrors = [];
|
||||
let normalErrors = [];
|
||||
|
||||
// to have a proper grouping of fatal errors and none fatal, we need to check
|
||||
// our errors for the fatal property
|
||||
if (errors && errors.length > 0) {
|
||||
for (let i = 0; i < errors.length; i += 1) {
|
||||
if (errors[i].fatal) {
|
||||
fatalErrors.push(errors[i]);
|
||||
} else {
|
||||
normalErrors.push(errors[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.fatalValidationErrors = fatalErrors;
|
||||
this.validationErrors = normalErrors;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (error.payload?.errors) {
|
||||
this.installError = error.payload.errors[0].message;
|
||||
return false;
|
||||
}
|
||||
|
||||
this.installError = error.message;
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -5,10 +5,14 @@
|
|||
<span>{{svg-jar "arrow-right"}}</span>
|
||||
{{@data.theme.name}}
|
||||
</h2>
|
||||
|
||||
<section class="view-actions">
|
||||
<button type="button" class="gh-btn gh-btn-primary" {{on "click" this.installTheme}}><span>Use {{@data.theme.name}}</span></button>
|
||||
</section>
|
||||
</GhCanvasHeader>
|
||||
|
||||
<section class="view-container">
|
||||
<GhBrowserPreview class="gh-branding-settings-previewcontainer">
|
||||
<GhBrowserPreview class="gh-branding-settings-previewcontainer" @title={{@data.theme.name}}>
|
||||
<iframe src={{@data.theme.previewUrl}} class="site-frame gh-branding-settings-preview"></iframe>
|
||||
</GhBrowserPreview>
|
||||
</section>
|
||||
|
|
30
ghost/admin/app/components/modals/design/view-theme.js
Normal file
30
ghost/admin/app/components/modals/design/view-theme.js
Normal file
|
@ -0,0 +1,30 @@
|
|||
import Component from '@glimmer/component';
|
||||
import {action} from '@ember/object';
|
||||
import {inject as service} from '@ember/service';
|
||||
|
||||
export default class ViewThemeModalComponent extends Component {
|
||||
@service modals;
|
||||
@service router;
|
||||
|
||||
willDestroy() {
|
||||
super.willDestroy(...arguments);
|
||||
|
||||
// leave install modal visiible if it's in the success state because
|
||||
// we're switching over to the design customisation screen in the bg
|
||||
// and don't want to auto-close when this modal closes
|
||||
if (this.installModal && !this.showingSuccessModal) {
|
||||
this.installModal.close();
|
||||
}
|
||||
}
|
||||
|
||||
@action
|
||||
installTheme() {
|
||||
this.installModal = this.modals.open('modals/design/install-theme', {
|
||||
theme: this.args.data.theme,
|
||||
onSuccess: () => {
|
||||
this.showingSuccessModal = true;
|
||||
this.router.transitionTo('settings.design');
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
|
@ -13,6 +13,9 @@ export default class ModalsService extends EPMModalsService {
|
|||
'modals/design/confirm-delete-theme': {
|
||||
className: 'fullscreen-modal-action fullscreen-modal-wide'
|
||||
},
|
||||
'modals/design/install-theme': {
|
||||
className: 'fullscreen-modal-action fullscreen-modal-wide'
|
||||
},
|
||||
'modals/design/theme-errors': {
|
||||
className: 'fullscreen-modal-action fullscreen-modal-wide'
|
||||
},
|
||||
|
|
|
@ -39,7 +39,7 @@ export default class ThemeManagementService extends Service {
|
|||
}
|
||||
|
||||
@task
|
||||
*activateTask(theme) {
|
||||
*activateTask(theme, options) {
|
||||
let resultModal = null;
|
||||
|
||||
try {
|
||||
|
@ -68,6 +68,7 @@ export default class ThemeManagementService extends Service {
|
|||
this.updatePreviewHtmlTask.perform();
|
||||
this.customThemeSettings.load();
|
||||
|
||||
if (!options.skipErrors) {
|
||||
const {warnings, errors} = activatedTheme;
|
||||
|
||||
if (!isEmpty(warnings) || !isEmpty(errors)) {
|
||||
|
@ -80,7 +81,9 @@ export default class ThemeManagementService extends Service {
|
|||
|
||||
yield resultModal;
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
if (!options.skipErrors) {
|
||||
if (isThemeValidationError(error)) {
|
||||
let errors = error.payload.errors[0].details.errors;
|
||||
let fatalErrors = [];
|
||||
|
@ -107,6 +110,7 @@ export default class ThemeManagementService extends Service {
|
|||
|
||||
yield resultModal;
|
||||
}
|
||||
}
|
||||
|
||||
throw error;
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue