package io.j99.idea.vue.module; import com.intellij.execution.ExecutionException; import com.intellij.execution.configurations.GeneralCommandLine; import com.intellij.execution.process.ProcessOutput; import com.intellij.ide.util.projectWizard.SettingsStep; import com.intellij.internal.statistic.UsageTrigger; import com.intellij.notification.NotificationType; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.ui.TextFieldWithBrowseButton; import com.intellij.openapi.ui.ValidationInfo; import com.intellij.openapi.util.Disposer; import com.intellij.openapi.util.io.FileUtil; import com.intellij.openapi.util.text.StringUtil; import com.intellij.platform.WebProjectGenerator; import com.intellij.ui.ColorUtil; import com.intellij.ui.DocumentAdapter; import com.intellij.ui.JBColor; import com.intellij.ui.components.JBLabel; import com.intellij.ui.components.JBList; import com.intellij.uiDesigner.core.GridConstraints; import com.intellij.util.ui.AsyncProcessIcon; import com.intellij.xml.util.XmlStringUtil; import io.j99.idea.vue.VueBundle; import io.j99.idea.vue.VueIcons; import io.j99.idea.vue.cli.nodejs.NodeRunner; import io.j99.idea.vue.component.VueProjectSettingsComponent; import io.j99.idea.vue.network.Network; import io.j99.idea.vue.network.model.TemplateModel; import io.j99.idea.vue.sdk.VueSdkUtils; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClients; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import javax.swing.*; import javax.swing.event.DocumentEvent; import javax.swing.event.ListSelectionEvent; import javax.swing.event.ListSelectionListener; import java.awt.*; import java.io.File; import java.io.IOException; import java.util.List; import java.util.Timer; import java.util.TimerTask; public class VueGeneratorPeer implements WebProjectGenerator.GeneratorPeer<VueProjectWizardData>{ private final VueProjectWizardData.Sdk sdk; private JPanel myMainPanel; private TextFieldWithBrowseButton nodePathTextWithBrowse; private JBLabel myVersionLabel; private JPanel myTemplatesPanel; private JPanel myLoadingTemplatesPanel; private JPanel myLoadedTemplatesPanel; private JBList myTemplatesList; private JBLabel myErrorLabel; // shown in IntelliJ IDEA only private JBLabel myVueVersionLable; private TextFieldWithBrowseButton vuePathTextWithBrowse; private java.util.Timer timer1; private java.util.Timer timer2; private boolean valitedateNode=false; private boolean valitedateVue=false; public interface RunTaskCallback{ void onEnd(ProcessOutput output); void onFaild(Exception e); } private class RunTask extends TimerTask{ public RunTask(GeneralCommandLine cmd, RunTaskCallback callback) { this.cmd = cmd; this.callback = callback; } private final GeneralCommandLine cmd; private final RunTaskCallback callback; @Override public void run() { try { ProcessOutput out = NodeRunner.execute(cmd, NodeRunner.TIME_OUT); callback.onEnd(out); } catch (ExecutionException e) { e.printStackTrace(); callback.onFaild(e); } } } public VueGeneratorPeer(VueProjectWizardData.Sdk sdk){ this.sdk=sdk; timer1=new java.util.Timer(true); timer2=new java.util.Timer(true); vuePathTextWithBrowse.setEnabled(false); myLoadingTemplatesPanel.setVisible(false); nodePathTextWithBrowse.getTextField().getDocument().addDocumentListener(new DocumentAdapter() { @Override protected void textChanged(DocumentEvent documentEvent) { timer1.cancel(); timer1=new Timer(); String path = nodePathTextWithBrowse.getText().trim(); if(new File(path).isDirectory()||!new File(path).exists())return; GeneralCommandLine cmd = new GeneralCommandLine(); cmd.setExePath(path); cmd.addParameter("--version"); RunTask task = new RunTask(cmd, new RunTaskCallback() { @Override public void onEnd(ProcessOutput output) { if (output.getExitCode() == 0) { valitedateNode=true; SwingUtilities.invokeLater(() -> { myVersionLabel.setText(output.getStdout().trim()); vuePathTextWithBrowse.setEnabled(true); }); } else { valitedateNode=false; SwingUtilities.invokeLater(() -> vuePathTextWithBrowse.setEnabled(false)); UsageTrigger.trigger(output.getStderr()); } } @Override public void onFaild(Exception e) { } }); timer1.schedule(task,1000); } }); vuePathTextWithBrowse.getTextField().getDocument().addDocumentListener(new DocumentAdapter() { @Override protected void textChanged(DocumentEvent documentEvent) { timer2.cancel(); timer2=new Timer(); String path = vuePathTextWithBrowse.getText().trim(); String nodePath = nodePathTextWithBrowse.getText().trim(); if(new File(path).isDirectory() || !new File(path).exists()){ myErrorLabel.setVisible(true); return; } GeneralCommandLine cmd = NodeRunner.createCommandLine(FileUtil.getTempDirectory(), nodePath, path); cmd.addParameter("-V"); RunTask task=new RunTask(cmd, new RunTaskCallback() { @Override public void onEnd(ProcessOutput output){ if(output.getExitCode()==0){ valitedateVue=true; SwingUtilities.invokeLater(() -> { myErrorLabel.setVisible(false); myVueVersionLable.setText(output.getStdout().trim()); }); loadVueTemplateList(); }else{ valitedateVue=false; UsageTrigger.trigger(output.getStderr()); } } @Override public void onFaild(Exception e) { } }); timer2.schedule(task,1000); } }); VueSdkUtils.initVueSdkControls(null,nodePathTextWithBrowse,vuePathTextWithBrowse); if(sdk!=null) { if(StringUtil.isNotEmpty(sdk.nodePath)) { nodePathTextWithBrowse.setText(sdk.nodePath); } if(StringUtil.isNotEmpty(sdk.vuePath)) { vuePathTextWithBrowse.setText(sdk.vuePath); } } } private void loadVueTemplateList() { SwingUtilities.invokeLater(() -> { myLoadingTemplatesPanel.setVisible(true); myLoadedTemplatesPanel.setVisible(false); }); AsyncProcessIcon asyncProcessIcon=new AsyncProcessIcon("Vue Template loading"); myLoadingTemplatesPanel.add(asyncProcessIcon,new GridConstraints()); asyncProcessIcon.resume(); ApplicationManager.getApplication().executeOnPooledThread(new Runnable() { @Override public void run() { try { List<TemplateModel> templates = new Network().listTemplate(); asyncProcessIcon.suspend(); Disposer.dispose(asyncProcessIcon); SwingUtilities.invokeLater(() -> { myLoadingTemplatesPanel.setVisible(false); myLoadedTemplatesPanel.setVisible(true); }); if(templates.size()==0){ VueProjectSettingsComponent.showNotification("Can't get Vue Templates,Please check your network!", NotificationType.INFORMATION); }else { DefaultListModel<TemplateModel> model = new DefaultListModel<>(); for(TemplateModel template:templates){ model.addElement(template); } SwingUtilities.invokeLater(new Runnable() { @Override public void run() { myTemplatesList.setModel(model); myTemplatesList.setCellRenderer(new DefaultListCellRenderer() { @Override public Component getListCellRendererComponent(JList<?> list, Object value, int index, boolean isSelected, boolean cellHasFocus) { final JLabel component = (JLabel) super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus); final TemplateModel template = (TemplateModel) value; component.setText(template.getDisplayName()); if(template.getName().startsWith("browserify")){ component.setIcon(VueIcons.BROWSERIFY_ICON); }else if(template.getName().startsWith("webpack")){ component.setIcon(VueIcons.WEBPACK_ICON); }else{ component.setIcon(VueIcons.VUE_ICON); } return component; } }); } }); } } catch (IOException e) { e.printStackTrace(); } } }); } @NotNull @Override public JComponent getComponent() { return myMainPanel; } @Override public void buildUI(@NotNull SettingsStep settingsStep) { settingsStep.addExpertField(VueBundle.message("vue.sdk.path"),vuePathTextWithBrowse); settingsStep.addExpertField(VueBundle.message("node.path"),nodePathTextWithBrowse); settingsStep.addSettingsComponent(myTemplatesPanel); } @NotNull @Override public VueProjectWizardData getSettings() { String vuePath = vuePathTextWithBrowse.getText().trim(); String nodePath = nodePathTextWithBrowse.getText().trim(); TemplateModel template = (TemplateModel)myTemplatesList.getSelectedValue(); return new VueProjectWizardData(new VueProjectWizardData.Sdk(nodePath, vuePath), new WebpackTemplate(template.getName(),template.getDescription())); } @Nullable @Override public ValidationInfo validate() { if(!valitedateNode){ return new ValidationInfo("Node path error",nodePathTextWithBrowse); } if(!valitedateVue){ return new ValidationInfo("Vue path error",vuePathTextWithBrowse); } return null; } @Override public boolean isBackgroundJobRunning() { return false; } @Override public void addSettingsStateListener(@NotNull WebProjectGenerator.SettingsStateListener stateListener) { nodePathTextWithBrowse.getTextField().getDocument().addDocumentListener(new DocumentAdapter() { protected void textChanged(final DocumentEvent e) { stateListener.stateChanged(validate() == null); } }); vuePathTextWithBrowse.getTextField().getDocument().addDocumentListener(new DocumentAdapter() { protected void textChanged(final DocumentEvent e) { stateListener.stateChanged(validate() == null); } }); myTemplatesList.addListSelectionListener(new ListSelectionListener() { @Override public void valueChanged(final ListSelectionEvent e) { stateListener.stateChanged(validate() == null); } }); } public boolean validateInIntelliJ() { final ValidationInfo info = validate(); if (info == null) { myErrorLabel.setVisible(false); return true; } else { myErrorLabel.setVisible(true); myErrorLabel.setText(XmlStringUtil.wrapInHtml("<font color='#" + ColorUtil.toHex(JBColor.RED) + "'><left>" + info.message + "</left></font>")); } return false; } }