Skip to content

Commit 631aba1

Browse files
committed
feat: New theme drag-and-drop upload UI
1 parent 2de7ed3 commit 631aba1

File tree

7 files changed

+454
-1
lines changed

7 files changed

+454
-1
lines changed

Gruntfile.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -360,6 +360,7 @@ module.exports = function(grunt) {
360360
[ WORKING_DIR + 'wp-admin/js/password-strength-meter.js' ]: [ './src/js/_enqueues/wp/password-strength-meter.js' ],
361361
[ WORKING_DIR + 'wp-admin/js/password-toggle.js' ]: [ './src/js/_enqueues/admin/password-toggle.js' ],
362362
[ WORKING_DIR + 'wp-admin/js/plugin-install.js' ]: [ './src/js/_enqueues/admin/plugin-install.js' ],
363+
[ WORKING_DIR + 'wp-admin/js/theme-upload.js' ]: [ './src/js/_enqueues/admin/theme-upload.js' ],
363364
[ WORKING_DIR + 'wp-admin/js/post.js' ]: [ './src/js/_enqueues/admin/post.js' ],
364365
[ WORKING_DIR + 'wp-admin/js/postbox.js' ]: [ './src/js/_enqueues/admin/postbox.js' ],
365366
[ WORKING_DIR + 'wp-admin/js/revisions.js' ]: [ './src/js/_enqueues/wp/revisions.js' ],
@@ -957,6 +958,7 @@ module.exports = function(grunt) {
957958
'src/wp-admin/js/nav-menu.js': 'src/js/_enqueues/lib/nav-menu.js',
958959
'src/wp-admin/js/password-strength-meter.js': 'src/js/_enqueues/wp/password-strength-meter.js',
959960
'src/wp-admin/js/plugin-install.js': 'src/js/_enqueues/admin/plugin-install.js',
961+
'src/wp-admin/js/theme-upload.js': 'src/js/_enqueues/admin/theme-upload.js',
960962
'src/wp-admin/js/post.js': 'src/js/_enqueues/admin/post.js',
961963
'src/wp-admin/js/postbox.js': 'src/js/_enqueues/admin/postbox.js',
962964
'src/wp-admin/js/revisions.js': 'src/js/_enqueues/wp/revisions.js',
Lines changed: 290 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,290 @@
1+
/**
2+
* @file Functionality for the theme upload screen.
3+
*
4+
* @output wp-admin/js/theme-upload.js
5+
*/
6+
7+
/* global theme_upload_intl, plupload, themeUploaderInit, ajaxurl, upload_theme_nonce, cancel_theme_overwrite_nonce, customize_url*/
8+
9+
function themeFileQueued( fileObj ) {
10+
jQuery( '#theme-upload-list' ).append( `
11+
<div class="theme" id="theme-item-${ fileObj.id }">
12+
<div class="theme-screenshot uploading">
13+
<div class="media-item"><div class="progress"><div class="percent">0%</div><div class="bar"></div></div></div>
14+
</div>
15+
<div class="theme-author"> </div>
16+
<div class="theme-id-container">
17+
<h3 class="theme-name">${ fileObj.name }</h3>
18+
<div class="theme-actions"></div>
19+
</div>
20+
</div>
21+
` );
22+
}
23+
24+
function buildComparisonTable( fileObj, data ) {
25+
return `
26+
<div id="theme-modal-window-${ fileObj.id }" style="display:none;">
27+
<table class="update-from-upload-comparison">
28+
<tbody>
29+
<tr>
30+
<th></th>
31+
<th>${ theme_upload_intl.current }</th>
32+
<th>${ theme_upload_intl.uploaded }</th>
33+
</tr>
34+
<tr>
35+
<td class="name-label">${ theme_upload_intl.theme_name }</td>
36+
<td>${ data.Name[ 0 ] }</td>
37+
<td>${ data.Name[ 1 ] }</td>
38+
</tr>
39+
<tr>
40+
<td class="name-label">${ theme_upload_intl.version }</td>
41+
<td>${ data.Version[ 0 ] }</td>
42+
<td>${ data.Version[ 1 ] }</td>
43+
</tr>
44+
<tr>
45+
<td class="name-label">${ theme_upload_intl.author }</td>
46+
<td>${ data.Author[ 0 ] }</td>
47+
<td>${ data.Author[ 1 ] }</td>
48+
</tr>
49+
<tr>
50+
<td class="name-label">${ theme_upload_intl.required_wordpress_version }</td>
51+
<td>${ data.RequiresWP[ 0 ] }</td>
52+
<td>${ data.RequiresWP[ 1 ] }</td>
53+
</tr>
54+
<tr>
55+
<td class="name-label">${ theme_upload_intl.required_php_version }</td>
56+
<td>${ data.RequiresPHP[ 0 ] }</td>
57+
<td>${ data.RequiresPHP[ 1 ] }</td>
58+
</tr>
59+
</tbody>
60+
</table>
61+
</div>
62+
63+
`;
64+
}
65+
function themeUploadProgress( up, file ) {
66+
const item = jQuery( '#theme-item-' + file.id );
67+
68+
jQuery( '.bar', item ).width( ( 200 * file.loaded ) / file.size );
69+
if ( 100 === file.percent ) {
70+
jQuery( '.percent', item ).html( theme_upload_intl.processing + '...' );
71+
} else {
72+
jQuery( '.percent', item ).html( file.percent + '%' );
73+
}
74+
}
75+
76+
function themeUploadSuccess( fileObj, serverData ) {
77+
const item = jQuery( '#theme-item-' + fileObj.id );
78+
const action_selector = jQuery( '.theme-actions', item );
79+
80+
const response_json = JSON.parse( serverData );
81+
const data = response_json.data;
82+
83+
jQuery( '.theme-author', item ).text( 'By ' + data.theme.Author );
84+
85+
jQuery( '.theme-name', item ).text( data.theme.Name );
86+
jQuery( '.theme-screenshot', item ).html( '' ).removeClass( 'uploading' );
87+
88+
item.append(
89+
`<div class="notice notice-success notice-alt"><p>Installed</p></div>`
90+
);
91+
if ( 'can_override' === data.successCode ) {
92+
if ( data.comparisonMessage.Downgrade ) {
93+
action_selector.append(
94+
`<button class="button activate-button overwrite-theme" data-attachment="${ data.attachment_id }"> ${ theme_upload_intl.downgrade } </button>`
95+
);
96+
} else {
97+
action_selector.append(
98+
`<button class="button activate-button overwrite-theme" data-attachment="${ data.attachment_id }"> ${ theme_upload_intl.upgrade } </button>`
99+
);
100+
}
101+
action_selector.append(
102+
buildComparisonTable( fileObj, data.comparisonMessage )
103+
);
104+
action_selector.append(
105+
`<button class="button activate-button cancel-overwrite" data-file="${ fileObj.id }" data-attachment="${ data.attachment_id }"> ${ theme_upload_intl.cancel } </button>`
106+
);
107+
108+
item.append(
109+
`<a href="#TB_inline?&inlineId=theme-modal-window-${ fileObj.id }&width=700" class="thickbox" ><span class="more-details">${theme_upload_intl.details}</span></a>`
110+
);
111+
jQuery( '.button.overwrite-theme' )
112+
.unbind()
113+
.click( function () {
114+
const button = jQuery( this );
115+
const attachment_id = button.data( 'attachment' );
116+
overwriteTheme( attachment_id, button );
117+
} );
118+
jQuery( '.button.cancel-overwrite' )
119+
.unbind()
120+
.click( function () {
121+
const button = jQuery( this );
122+
const attachment_id = button.data( 'attachment' );
123+
const file_id = button.data( 'file' );
124+
cancelOverwriteTheme( file_id, attachment_id, button );
125+
} );
126+
} else {
127+
if ( data.screenshot ) {
128+
jQuery( '.theme-screenshot', item ).html(
129+
`<img src="${ data.screenshot }" class="theme-icon" alt="">`
130+
);
131+
}
132+
// The activate nonce must be in the format 'switch-theme_' . $_GET['stylesheet']. It is a bit tricky to generate this dynamically via javascript. So I will comment this out till I find a switable solution
133+
// action_selector.append(`<a class="button activate" href="${theme_url}?action=activate&amp;stylesheet=${data.stylesheet}&amp;_wpnonce=${activate_theme_nonce}" aria-label="${ theme_upload_intl.activate } ${ data.theme.Name }">${ theme_upload_intl.activate } </a>`);
134+
135+
action_selector.append(
136+
`<a class="button load-customize" href="${customize_url}?theme=${data.stylesheet}&amp;return=%2Fwp-admin%2Ftheme-install.php">${theme_upload_intl.live_preview}</a>`
137+
);
138+
}
139+
}
140+
141+
function themeUploadError( fileObj, errorCode, message ) {
142+
if ( message ) {
143+
const item = jQuery( '#theme-item-' + fileObj.id );
144+
const selector = jQuery(
145+
'.theme-screenshot',
146+
item
147+
);
148+
149+
const responseJSON = JSON.parse( message );
150+
if (
151+
responseJSON &&
152+
responseJSON.data &&
153+
responseJSON.data.errorMessage
154+
) {
155+
selector.html(
156+
`<div class="notice notice-error update-nag inline">${ responseJSON.data.errorMessage }</div>`
157+
);
158+
} else {
159+
selector.html(
160+
`<div class="notice notice-error update-nag inline">${ theme_upload_intl.generic_error }</div>`
161+
);
162+
}
163+
}
164+
}
165+
166+
function overwriteTheme( attachment_id, button ) {
167+
const formData = new FormData();
168+
formData.append( '_wpnonce', upload_theme_nonce );
169+
formData.append( 'action', 'upload-theme' );
170+
button.prop( 'disabled', true );
171+
button.text( theme_upload_intl.processing + '...' );
172+
const cancel_button = button.parent().find('.cancel-overwrite');
173+
cancel_button.prop( 'disabled', true );
174+
175+
176+
jQuery.ajax( {
177+
type: 'POST',
178+
url: ajaxurl + '?package=' + attachment_id + '&overwrite=update-theme',
179+
data: formData,
180+
processData: false, // Important: tell jQuery not to process the data.
181+
contentType: false, // Important: tell jQuery not to set contentType.
182+
183+
success: function () {
184+
button.text( theme_upload_intl.updated );
185+
},
186+
error: function () {
187+
button.prop( 'disabled', false );
188+
cancel_button.prop( 'disabled', false );
189+
button.text( theme_upload_intl.activation_failed );
190+
},
191+
} );
192+
}
193+
194+
function cancelOverwriteTheme( file_id, attachment_id, button ) {
195+
const formData = new FormData();
196+
formData.append( '_wpnonce', cancel_theme_overwrite_nonce );
197+
formData.append( 'action', 'cancel-theme-overwrite' );
198+
button.prop( 'disabled', true );
199+
button.text( theme_upload_intl.processing + '...' );
200+
const overwrite_button = button.parent().find('.overwrite-theme');
201+
overwrite_button.prop( 'disabled', true );
202+
203+
jQuery.ajax( {
204+
type: 'POST',
205+
url: ajaxurl + '?package=' + attachment_id,
206+
data: formData,
207+
processData: false, // Important: tell jQuery not to process the data.
208+
contentType: false, // Important: tell jQuery not to set contentType.
209+
210+
success: function () {
211+
// Remove element from the dom.
212+
jQuery( '#theme-item-' + file_id ).remove();
213+
},
214+
error: function () {
215+
button.prop( 'disabled', false );
216+
overwrite_button.prop( 'disabled', false );
217+
button.text( theme_upload_intl.cancel_failed );
218+
},
219+
} );
220+
}
221+
jQuery( function ( $ ) {
222+
const uploader_init = function () {
223+
const uploader = new plupload.Uploader( themeUploaderInit );
224+
225+
uploader.bind( 'Init', function ( up ) {
226+
var uploaddiv = $( '#plupload-upload-ui' );
227+
228+
if (
229+
up.features.dragdrop &&
230+
! $( document.body ).hasClass( 'mobile' )
231+
) {
232+
uploaddiv.addClass( 'drag-drop' );
233+
234+
$( '#drag-drop-area' )
235+
.on( 'dragover.wp-uploader', function () {
236+
// dragenter doesn't fire right :(
237+
uploaddiv.addClass( 'drag-over' );
238+
} )
239+
.on(
240+
'dragleave.wp-uploader, drop.wp-uploader',
241+
function () {
242+
uploaddiv.removeClass( 'drag-over' );
243+
}
244+
);
245+
} else {
246+
uploaddiv.removeClass( 'drag-drop' );
247+
$( '#drag-drop-area' ).off( '.wp-uploader' );
248+
}
249+
250+
if ( up.runtime === 'html4' ) {
251+
$( '.upload-flash-bypass' ).hide();
252+
}
253+
} );
254+
255+
uploader.bind( 'postinit', function ( up ) {
256+
up.refresh();
257+
} );
258+
259+
uploader.init();
260+
261+
uploader.bind( 'FilesAdded', function ( up, files ) {
262+
plupload.each( files, function ( file ) {
263+
if ( file.type !== 'application/zip' ) {
264+
// Ignore zip files
265+
return;
266+
}
267+
268+
themeFileQueued( file );
269+
} );
270+
271+
up.refresh();
272+
up.start();
273+
} );
274+
275+
uploader.bind( 'UploadProgress', function ( up, file ) {
276+
themeUploadProgress( up, file );
277+
} );
278+
279+
uploader.bind( 'Error', function ( up, error ) {
280+
themeUploadError( error.file, error.code, error.response );
281+
up.refresh();
282+
} );
283+
284+
uploader.bind( 'FileUploaded', function ( up, file, response ) {
285+
themeUploadSuccess( file, response.response );
286+
} );
287+
};
288+
289+
uploader_init();
290+
} );

src/wp-admin/admin-ajax.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,8 @@
117117
'parse-media-shortcode',
118118
'destroy-sessions',
119119
'install-plugin',
120+
'upload-theme',
121+
'cancel-theme-overwrite',
120122
'activate-plugin',
121123
'update-plugin',
122124
'crop-image',

src/wp-admin/css/themes.css

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,12 @@ body.js .theme-browser.search-loading {
146146
transition: opacity 0.2s ease-in-out;
147147
}
148148

149+
.theme-browser .theme .theme-screenshot.uploading {
150+
display: flex;
151+
justify-content: center;
152+
align-items: center;
153+
}
154+
149155
.theme-browser .theme:hover .theme-screenshot,
150156
.theme-browser .theme.focus .theme-screenshot {
151157
background: #fff;

0 commit comments

Comments
 (0)