mirror of
https://github.com/TryGhost/Ghost.git
synced 2024-11-25 19:48:50 +03:00
Added upload progress stage to members importer
no issue - As the number of states for the component has become larger introduced a concept of "stages" on component level to easier track UI changes.
This commit is contained in:
parent
dc82014283
commit
3086ac1439
@ -1,7 +1,11 @@
|
|||||||
<header class="modal-header" data-test-modal="import-members">
|
<header class="modal-header" data-test-modal="import-members">
|
||||||
<h1>
|
<h1>
|
||||||
{{#if this.importResponse}}
|
{{#if this.summary}}
|
||||||
Import complete{{unless this.importResponse.invalid.count "!"}}
|
Import complete{{unless this.importResponse.invalid.count "!"}}
|
||||||
|
{{/if}}
|
||||||
|
|
||||||
|
{{#if this.uploading}}
|
||||||
|
Importing members
|
||||||
{{else}}
|
{{else}}
|
||||||
Import members
|
Import members
|
||||||
{{/if}}
|
{{/if}}
|
||||||
@ -9,127 +13,131 @@
|
|||||||
</header>
|
</header>
|
||||||
<a class="close" href="" role="button" title="Close" {{action "closeModal"}}>{{svg-jar "close"}}<span class="hidden">Close</span></a>
|
<a class="close" href="" role="button" title="Close" {{action "closeModal"}}>{{svg-jar "close"}}<span class="hidden">Close</span></a>
|
||||||
|
|
||||||
{{#if this.importResponse}}
|
<div class="modal-body">
|
||||||
{{!-- post upload step with import stats --}}
|
{{#if (and this.filePresent (not this.failureMessage))}}
|
||||||
|
{{#if this.validating}}
|
||||||
<div class="modal-body">
|
{{#if validationErrors}}
|
||||||
|
<div class="failed flex items-start gh-members-upload-errorcontainer {{if this.importDisabled "error" "warning"}}">
|
||||||
{{!-- Summary --}}
|
<div class="mr3">
|
||||||
<div class="ba b--whitegrey br3 middarkgrey bg-whitegrey-l2">
|
{{#if this.importDisabled}}
|
||||||
<div class="flex items-start">
|
{{svg-jar "warning" class="nudge-top--2 w5 h5 fill-red"}}
|
||||||
<div class="w-50 gh-member-import-result-summary">
|
{{else}}
|
||||||
<h2>{{format-number this.importResponse.imported.count}}</h2>
|
{{svg-jar "warning" class="nudge-top--2 w5 h5 fill-yellow-d1"}}
|
||||||
<p>{{pluralize this.importResponse.imported.count "Member" without-count=true}} added</p>
|
{{/if}}
|
||||||
</div>
|
</div>
|
||||||
<div class="bl b--whitegrey w-50 gh-member-import-result-summary">
|
<div class="ma0">
|
||||||
<h2>{{format-number this.importResponse.invalid.count}}</h2>
|
<p class="ma0 pa0 flex-grow w-100">The CSV contains errors! {{unless this.importDisabled "Some members will not be imported."}}</p>
|
||||||
<p>{{pluralize this.importResponse.invalid.count "Error" without-count=true}}</p>
|
{{#if validationErrors}}
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{{#if this.importResponse.invalid.count}}
|
|
||||||
<div class="mt6 gh-members-upload-errorcontainer error">
|
|
||||||
<div class="flex items-start">
|
|
||||||
{{svg-jar "warning" class="w5 h5 fill-red nudge-top--3 mr3"}}
|
|
||||||
<div class="flex-grow w-100">
|
|
||||||
<p class="ma0 pa0">{{format-number this.importResponse.invalid.count}} {{pluralize this.importResponse.invalid.count "member" without-count=true}} were skipped due to the following errors:</p>
|
|
||||||
{{#if this.config.enableDeveloperExperiments}}
|
|
||||||
<ul class="ma0 pa0 mt4 list bt b--whitegrey">
|
<ul class="ma0 pa0 mt4 list bt b--whitegrey">
|
||||||
{{#each this.importResponse.invalid.errors as |error|}}
|
{{#each validationErrors as |error|}}
|
||||||
<li class="gh-members-import-errormessage">{{error.message}} <span class="fw6">({{format-number error.count}})</span></li>
|
<li class="gh-members-import-errormessage">
|
||||||
|
<span>{{{error.message}}}</span>
|
||||||
|
{{#if error.context}}
|
||||||
|
<p class="gh-members-import-errorcontext">{{{error.context}}}</p>
|
||||||
|
{{/if}}
|
||||||
|
</li>
|
||||||
{{/each}}
|
{{/each}}
|
||||||
</ul>
|
</ul>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{{else}}
|
||||||
|
<div class="bg-whitegrey-l2 ba b--whitegrey br3 gh-image-uploader gh-members-import-spinner">
|
||||||
|
<div class="gh-loading-content">
|
||||||
|
<div class="gh-loading-spinner"></div>
|
||||||
|
</div>
|
||||||
|
<div class="description midgrey">Validating...</div>
|
||||||
|
</div>
|
||||||
|
{{/if}}
|
||||||
|
{{/if}}
|
||||||
|
|
||||||
|
{{#if this.customizing}}
|
||||||
|
<GhFormGroup>
|
||||||
|
{{#if this.config.enableDeveloperExperiments}}
|
||||||
|
<h4 class="fw6 f8 dib mb1">Mapping</h4>
|
||||||
|
<div class="gh-members-import-scrollarea">
|
||||||
|
<GhMembersImportTable
|
||||||
|
@importData={{this.fileData}}
|
||||||
|
@mapping={{this.mapping}}
|
||||||
|
@updateMapping={{action "updateMapping"}}/>
|
||||||
|
</div>
|
||||||
|
<p>Match the fields in your uploaded file to Ghost members.</p>
|
||||||
|
{{else}}
|
||||||
|
<div class="bg-whitegrey-l2 ba b--whitegrey br3">
|
||||||
|
<div class="flex flex-column items-center justify-center gh-members-import-file">
|
||||||
|
{{svg-jar "file-tabular-data" class="w9 h9 mb1 stroke-midgrey"}}
|
||||||
|
<div class="description midgrey">{{this.file.name}}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{/if}}
|
||||||
|
|
||||||
|
<div class="mt4">
|
||||||
|
<label for="label-input"><span class="fw6 f8 dib mb1">Labels</span></label>
|
||||||
|
<GhMemberLabelInput @member={{this.labels}} @triggerId="label-input" />
|
||||||
|
<p>Will be applied to all newly imported members</p>
|
||||||
|
</div>
|
||||||
|
</GhFormGroup>
|
||||||
|
{{/if}}
|
||||||
|
|
||||||
|
{{#if this.uploading}}
|
||||||
|
<div class="progress-container">
|
||||||
|
<div class="gh-progress-container-progress">
|
||||||
|
<div class="gh-progress-bar {{if this.failureMessage "-error"}}" style={{this.progressStyle}}></div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
</div>
|
|
||||||
{{else}}
|
{{#if this.summary}}
|
||||||
<div class="modal-body">
|
<div class="ba b--whitegrey br3 middarkgrey bg-whitegrey-l2">
|
||||||
{{#if (and this.filePresent (not this.failureMessage))}}
|
<div class="flex items-start">
|
||||||
{{#if this.validating}}
|
<div class="w-50 gh-member-import-result-summary">
|
||||||
{{#if validationErrors}}
|
<h2>{{format-number this.importResponse.imported.count}}</h2>
|
||||||
<div class="failed flex items-start gh-members-upload-errorcontainer {{if this.importDisabled "error" "warning"}}">
|
<p>{{pluralize this.importResponse.imported.count "Member" without-count=true}} added</p>
|
||||||
<div class="mr3">
|
</div>
|
||||||
{{#if this.importDisabled}}
|
<div class="bl b--whitegrey w-50 gh-member-import-result-summary">
|
||||||
{{svg-jar "warning" class="nudge-top--2 w5 h5 fill-red"}}
|
<h2>{{format-number this.importResponse.invalid.count}}</h2>
|
||||||
{{else}}
|
<p>{{pluralize this.importResponse.invalid.count "Error" without-count=true}}</p>
|
||||||
{{svg-jar "warning" class="nudge-top--2 w5 h5 fill-yellow-d1"}}
|
</div>
|
||||||
{{/if}}
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="ma0">
|
|
||||||
<p class="ma0 pa0 flex-grow w-100">The CSV contains errors! {{unless this.importDisabled "Some members will not be imported."}}</p>
|
{{#if this.importResponse.invalid.count}}
|
||||||
{{#if validationErrors}}
|
<div class="mt6 gh-members-upload-errorcontainer error">
|
||||||
|
<div class="flex items-start">
|
||||||
|
{{svg-jar "warning" class="w5 h5 fill-red nudge-top--3 mr3"}}
|
||||||
|
<div class="flex-grow w-100">
|
||||||
|
<p class="ma0 pa0">{{format-number this.importResponse.invalid.count}} {{pluralize this.importResponse.invalid.count "member" without-count=true}} were skipped due to the following errors:</p>
|
||||||
|
{{#if this.config.enableDeveloperExperiments}}
|
||||||
<ul class="ma0 pa0 mt4 list bt b--whitegrey">
|
<ul class="ma0 pa0 mt4 list bt b--whitegrey">
|
||||||
{{#each validationErrors as |error|}}
|
{{#each this.importResponse.invalid.errors as |error|}}
|
||||||
<li class="gh-members-import-errormessage">
|
<li class="gh-members-import-errormessage">{{error.message}} <span class="fw6">({{format-number error.count}})</span></li>
|
||||||
<span>{{{error.message}}}</span>
|
|
||||||
{{#if error.context}}
|
|
||||||
<p class="gh-members-import-errorcontext">{{{error.context}}}</p>
|
|
||||||
{{/if}}
|
|
||||||
</li>
|
|
||||||
{{/each}}
|
{{/each}}
|
||||||
</ul>
|
</ul>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{{else}}
|
|
||||||
<div class="bg-whitegrey-l2 ba b--whitegrey br3 gh-image-uploader gh-members-import-spinner">
|
|
||||||
<div class="gh-loading-content">
|
|
||||||
<div class="gh-loading-spinner"></div>
|
|
||||||
</div>
|
|
||||||
<div class="description midgrey">Validating...</div>
|
|
||||||
</div>
|
|
||||||
{{/if}}
|
|
||||||
{{else}}
|
|
||||||
<GhFormGroup>
|
|
||||||
{{#if this.config.enableDeveloperExperiments}}
|
|
||||||
<h4 class="fw6 f8 dib mb1">Mapping</h4>
|
|
||||||
<div class="gh-members-import-scrollarea">
|
|
||||||
<GhMembersImportTable
|
|
||||||
@importData={{this.fileData}}
|
|
||||||
@mapping={{this.mapping}}
|
|
||||||
@updateMapping={{action "updateMapping"}}/>
|
|
||||||
</div>
|
|
||||||
<p>Match the fields in your uploaded file to Ghost members.</p>
|
|
||||||
{{else}}
|
|
||||||
<div class="bg-whitegrey-l2 ba b--whitegrey br3">
|
|
||||||
<div class="flex flex-column items-center justify-center gh-members-import-file">
|
|
||||||
{{svg-jar "file-tabular-data" class="w9 h9 mb1 stroke-midgrey"}}
|
|
||||||
<div class="description midgrey">{{this.file.name}}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{{/if}}
|
|
||||||
|
|
||||||
<div class="mt4">
|
|
||||||
<label for="label-input"><span class="fw6 f8 dib mb1">Labels</span></label>
|
|
||||||
<GhMemberLabelInput @member={{this.labels}} @triggerId="label-input" />
|
|
||||||
<p>Will be applied to all newly imported members</p>
|
|
||||||
</div>
|
|
||||||
</GhFormGroup>
|
|
||||||
{{/if}}
|
|
||||||
{{else}}
|
|
||||||
{{#if this.failureMessage}}
|
|
||||||
<div class="failed flex items-start gh-members-upload-errorcontainer error">
|
|
||||||
<div class="mr2">{{svg-jar "warning" class="nudge-top--2 w4 h4 fill-red"}}</div>
|
|
||||||
<p class="ma0 pa0">{{this.failureMessage}}</p>
|
|
||||||
</div>
|
</div>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
<div class="upload-form bg-whitegrey-l2 ba b--whitegrey br3">
|
{{/if}}
|
||||||
<section class="gh-image-uploader gh-members-import-uploader {{this.dragClass}}">
|
{{else}}
|
||||||
<GhFileInput @multiple={{false}} @alt={{this.labelText}} @action={{action "fileSelected"}} @accept={{this.accept}}>
|
{{#if this.failureMessage}}
|
||||||
<div class="flex flex-column items-center">
|
<div class="failed flex items-start gh-members-upload-errorcontainer error">
|
||||||
{{svg-jar "upload" class="w9 h9 mb1 stroke-midgrey"}}
|
<div class="mr2">{{svg-jar "warning" class="nudge-top--2 w4 h4 fill-red"}}</div>
|
||||||
<div class="description midgrey">{{this.labelText}}</div>
|
<p class="ma0 pa0">{{this.failureMessage}}</p>
|
||||||
</div>
|
|
||||||
</GhFileInput>
|
|
||||||
</section>
|
|
||||||
</div>
|
</div>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
</div>
|
<div class="upload-form bg-whitegrey-l2 ba b--whitegrey br3">
|
||||||
{{/if}}
|
<section class="gh-image-uploader gh-members-import-uploader {{this.dragClass}}">
|
||||||
|
<GhFileInput @multiple={{false}} @alt={{this.labelText}} @action={{action "fileSelected"}} @accept={{this.accept}}>
|
||||||
|
<div class="flex flex-column items-center">
|
||||||
|
{{svg-jar "upload" class="w9 h9 mb1 stroke-midgrey"}}
|
||||||
|
<div class="description midgrey">{{this.labelText}}</div>
|
||||||
|
</div>
|
||||||
|
</GhFileInput>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
{{/if}}
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="modal-footer {{if (and this.filePresent (not this.failureMessage) (not this.importResponse)) "modal-footer-spread"}}">
|
<div class="modal-footer {{if (and this.filePresent (not this.failureMessage) (not this.importResponse)) "modal-footer-spread"}}">
|
||||||
{{#if this.importResponse}}
|
{{#if this.importResponse}}
|
||||||
@ -148,7 +156,9 @@
|
|||||||
</button>
|
</button>
|
||||||
{{/unless}}
|
{{/unless}}
|
||||||
{{/if}}
|
{{/if}}
|
||||||
{{else}}
|
{{/if}}
|
||||||
|
|
||||||
|
{{#if this.customizing}}
|
||||||
<button {{action "reset"}} class="gh-btn" data-test-button="close-import-members">
|
<button {{action "reset"}} class="gh-btn" data-test-button="close-import-members">
|
||||||
<span>Start over</span>
|
<span>Start over</span>
|
||||||
</button>
|
</button>
|
||||||
|
@ -71,13 +71,17 @@ export default ModalComponent.extend({
|
|||||||
|
|
||||||
labelText: 'Select or drag-and-drop a CSV file',
|
labelText: 'Select or drag-and-drop a CSV file',
|
||||||
|
|
||||||
|
// import stages, default is "CSV file selection"
|
||||||
|
validating: false,
|
||||||
|
customizing: false,
|
||||||
|
uploading: false,
|
||||||
|
summary: false,
|
||||||
|
|
||||||
dragClass: null,
|
dragClass: null,
|
||||||
file: null,
|
file: null,
|
||||||
fileData: null,
|
fileData: null,
|
||||||
mapping: null,
|
mapping: null,
|
||||||
paramName: 'membersfile',
|
paramName: 'membersfile',
|
||||||
validating: false,
|
|
||||||
uploading: false,
|
|
||||||
uploadPercentage: 0,
|
uploadPercentage: 0,
|
||||||
importResponse: null,
|
importResponse: null,
|
||||||
failureMessage: null,
|
failureMessage: null,
|
||||||
@ -161,6 +165,7 @@ export default ModalComponent.extend({
|
|||||||
// TODO: remove "if" below once import validations are production ready
|
// TODO: remove "if" below once import validations are production ready
|
||||||
if (this.config.get('enableDeveloperExperiments')) {
|
if (this.config.get('enableDeveloperExperiments')) {
|
||||||
this.set('validating', true);
|
this.set('validating', true);
|
||||||
|
|
||||||
papaparse.parse(file, {
|
papaparse.parse(file, {
|
||||||
header: true,
|
header: true,
|
||||||
skipEmptyLines: true,
|
skipEmptyLines: true,
|
||||||
@ -175,12 +180,15 @@ export default ModalComponent.extend({
|
|||||||
this._importValidationFailed(validationErrors);
|
this._importValidationFailed(validationErrors);
|
||||||
} else {
|
} else {
|
||||||
this.set('validating', false);
|
this.set('validating', false);
|
||||||
|
this.set('customizing', true);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
error: (error) => {
|
error: (error) => {
|
||||||
this._validationFailed(error);
|
this._validationFailed(error);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
} else {
|
||||||
|
this.set('customizing', true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -192,6 +200,11 @@ export default ModalComponent.extend({
|
|||||||
this.set('fileData', null);
|
this.set('fileData', null);
|
||||||
this.set('mapping', null);
|
this.set('mapping', null);
|
||||||
this.set('validationErrors', null);
|
this.set('validationErrors', null);
|
||||||
|
|
||||||
|
this.set('validating', false);
|
||||||
|
this.set('customizing', false);
|
||||||
|
this.set('uploading', false);
|
||||||
|
this.set('summary', false);
|
||||||
},
|
},
|
||||||
|
|
||||||
upload() {
|
upload() {
|
||||||
@ -202,6 +215,7 @@ export default ModalComponent.extend({
|
|||||||
|
|
||||||
continueImport() {
|
continueImport() {
|
||||||
this.set('validating', false);
|
this.set('validating', false);
|
||||||
|
this.set('customizing', true);
|
||||||
},
|
},
|
||||||
|
|
||||||
confirm() {
|
confirm() {
|
||||||
@ -280,6 +294,7 @@ export default ModalComponent.extend({
|
|||||||
},
|
},
|
||||||
|
|
||||||
_uploadStarted() {
|
_uploadStarted() {
|
||||||
|
this.set('customizing', false);
|
||||||
this.set('uploading', true);
|
this.set('uploading', true);
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -300,6 +315,7 @@ export default ModalComponent.extend({
|
|||||||
|
|
||||||
_uploadFinished() {
|
_uploadFinished() {
|
||||||
this.set('uploading', false);
|
this.set('uploading', false);
|
||||||
|
this.set('summary', true);
|
||||||
},
|
},
|
||||||
|
|
||||||
_importValidationFailed(errors) {
|
_importValidationFailed(errors) {
|
||||||
|
Loading…
Reference in New Issue
Block a user