package org.activityinfo.ui.client.page.config.design.importer; import com.google.common.base.Joiner; import com.google.common.base.Strings; import com.google.gwt.core.client.GWT; import com.google.gwt.dom.client.DivElement; import com.google.gwt.dom.client.ParagraphElement; import com.google.gwt.dom.client.UListElement; import com.google.gwt.event.dom.client.ChangeEvent; import com.google.gwt.event.dom.client.ClickEvent; import com.google.gwt.event.dom.client.ClickHandler; import com.google.gwt.event.dom.client.KeyPressEvent; import com.google.gwt.safehtml.shared.SafeHtml; import com.google.gwt.safehtml.shared.SafeHtmlBuilder; import com.google.gwt.uibinder.client.UiBinder; import com.google.gwt.uibinder.client.UiField; import com.google.gwt.uibinder.client.UiHandler; import com.google.gwt.user.client.Timer; import com.google.gwt.user.client.rpc.AsyncCallback; import com.google.gwt.user.client.ui.*; import org.activityinfo.promise.Promise; import org.activityinfo.i18n.shared.I18N; import org.activityinfo.ui.client.component.importDialog.data.PastedTable; import org.activityinfo.ui.client.page.config.design.importer.SchemaImporter.ProgressListener; import org.activityinfo.ui.client.style.BaseStylesheet; import org.activityinfo.ui.client.widget.ModalDialog; import org.activityinfo.ui.client.widget.ProgressBar; import java.util.logging.Level; import java.util.logging.Logger; public class SchemaImportDialog { private static final Logger LOGGER = Logger.getLogger(SchemaImportDialog.class.getName()); private enum State { /** * Initial, empty state, providing instructions to user */ INIT, /** * The user has provided valid input */ INPUT_VALID, /** * The user has provided invalid input */ INPUT_INVALID, /** * There are warnings to display to the user * before continuing */ IMPORT_WARNINGS, /** * Import is currently in progress */ IMPORTING, /** * Import has succeeded */ IMPORT_SUCCEEDED, /** * Import has failed */ IMPORT_FAILED } private static ImportSchemaUiBinder uiBinder = GWT.create(ImportSchemaUiBinder.class); interface ImportSchemaUiBinder extends UiBinder<Widget, SchemaImportDialog> { } private Promise<Void> promise; private SchemaImporter importer; @UiField FlowPanel container; // first page @UiField HTMLPanel inputPanel; @UiField TextArea textArea; @UiField DivElement textAreaGroup; @UiField ParagraphElement textAreaHelp; // warning panel @UiField UListElement warningList; // second page @UiField ProgressBar progressBar; @UiField HTMLPanel progressPanel; @UiField HTMLPanel successPanel; @UiField HTMLPanel failurePanel; @UiField ScrollPanel warningPanel; private ModalDialog dialog; private State currentState = State.INIT; private Timer validateTimer; public SchemaImportDialog(SchemaImporter importer) { this.importer = importer; BaseStylesheet.INSTANCE.ensureInjected(); Widget content = uiBinder.createAndBindUi(this); dialog = new ModalDialog(content); dialog.setDialogTitle(I18N.CONSTANTS.importSchemaDialogTitle()); dialog.getPrimaryButton().addClickHandler(new ClickHandler() { @Override public void onClick(ClickEvent event) { onPrimaryButtonClicked(); } }); dialog.getBackButton().addClickHandler(new ClickHandler() { @Override public void onClick(ClickEvent event) { onBackButtonClicked(); } }); } public Promise<Void> show() { promise = new Promise<Void>(); dialog.show(); textArea.setFocus(true); return promise; } private void onPrimaryButtonClicked() { switch(currentState) { case INIT: case INPUT_INVALID: validateInput(); break; case INPUT_VALID: if(importer.getWarnings().isEmpty()) { startImport(); } else { showWarnings(); } break; case IMPORT_WARNINGS: startImport(); break; case IMPORT_SUCCEEDED: dialog.hide(); break; case IMPORT_FAILED: startImport(); break; } } private void onBackButtonClicked() { switch(currentState) { case IMPORT_WARNINGS: warningPanel.setVisible(false); break; case IMPORT_FAILED: failurePanel.setVisible(false); break; } inputPanel.setVisible(true); validateInput(); dialog.getPrimaryButton().setText(I18N.CONSTANTS.ok()); dialog.hideBackButton(); } @UiHandler("textArea") public void onTextAreaChanged(ChangeEvent changeEvent) { validateInput(); } @UiHandler("textArea") public void onKeyPressed(KeyPressEvent keyEvent) { scheduleInputValidation(); } private void scheduleInputValidation() { if(validateTimer == null) { validateTimer = new Timer() { @Override public void run() { validateInput(); } }; } else { validateTimer.cancel(); } validateTimer.schedule(200); } private void validateInput() { if(isTextAreaEmpty()) { onInputInvalid(I18N.CONSTANTS.schemaImportEmpty()); } else { try { tryValidateImport(); } catch(Exception e) { LOGGER.log(Level.SEVERE, "Exception while validating input", e); onInputInvalid("Eek! Exception: " + e.getMessage()); } } } private void tryValidateImport() { PastedTable source = new PastedTable(textArea.getText()); if(source.getColumns().size() < 2 || source.getRows().size() <= 1) { onInputInvalid(I18N.CONSTANTS.invalidTableData()); return; } if(!importer.parseColumns(source)) { onInputInvalid(I18N.MESSAGES.missingColumns( Joiner.on(", ").join(importer.getMissingColumns()))); return; } importer.processRows(); onInputValid(); } private void showWarnings() { currentState = State.IMPORT_WARNINGS; dialog.getBackButton().setVisible(true); dialog.getPrimaryButton().setText(I18N.CONSTANTS.ignoreImportWarnings()); inputPanel.setVisible(false); warningPanel.setVisible(true); warningList.setInnerSafeHtml(composeWarnings()); } private SafeHtml composeWarnings() { SafeHtmlBuilder html = new SafeHtmlBuilder(); for(SafeHtml warning : importer.getWarnings()) { html.append(warning); } return html.toSafeHtml(); } private boolean isTextAreaEmpty() { return Strings.nullToEmpty(textArea.getText()).trim().length() == 0; } private void onInputValid() { currentState = State.INPUT_VALID; textAreaGroup.removeClassName("has-error"); textAreaGroup.addClassName("has-success"); textAreaHelp.setInnerText(I18N.CONSTANTS.validSchemaImport()); } private void onInputInvalid(String message) { currentState = State.INPUT_INVALID; textAreaGroup.addClassName("has-error"); textAreaGroup.removeClassName("has-success"); textAreaHelp.setInnerText(message); textArea.setFocus(true); } private void startImport() { currentState = State.IMPORTING; inputPanel.setVisible(false); warningPanel.setVisible(false); progressPanel.setVisible(true); dialog.hideBackButton(); dialog.disablePrimaryButton(); dialog.disableCancelButton(); importer.clearWarnings(); importer.processRows(); importer.setProgressListener(new ProgressListener() { @Override public void submittingBatch(int batchNumber, int batchCount) { int percent = (int) (((double) batchNumber) / ((double) batchCount) * 100d); progressBar.setValue(percent); } }); importer.persist(new AsyncCallback<Void>() { @Override public void onSuccess(Void result) { promise.resolve(null); onImportSucceeded(); } @Override public void onFailure(Throwable caught) { onImportFailed(); } }); } private void onImportSucceeded() { currentState = State.IMPORT_SUCCEEDED; progressPanel.setVisible(false); successPanel.setVisible(true); dialog.hideCancelButton(); dialog.getPrimaryButton().setEnabled(true); dialog.getPrimaryButton().setText(I18N.CONSTANTS.close()); } private void onImportFailed() { currentState = State.IMPORT_FAILED; progressPanel.setVisible(false); failurePanel.setVisible(true); dialog.getPrimaryButton().setEnabled(true); dialog.getPrimaryButton().setText(I18N.CONSTANTS.retry()); } }