1 /*
2  * AppNameTextbox.java
3  *
4  * Copyright (C) 2021 by RStudio, PBC
5  *
6  * Unless you have received this program directly from RStudio pursuant
7  * to the terms of a commercial license agreement with RStudio, then
8  * this program is licensed to you under the terms of version 3 of the
9  * GNU Affero General Public License. This program is distributed WITHOUT
10  * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
11  * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
12  * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
13  *
14  */
15 package org.rstudio.studio.client.rsconnect.ui;
16 
17 import org.rstudio.core.client.CommandWithArg;
18 import org.rstudio.core.client.StringUtil;
19 import org.rstudio.core.client.dom.DomUtils;
20 import org.rstudio.studio.client.rsconnect.model.RSConnectAppName;
21 
22 import com.google.gwt.core.client.GWT;
23 import com.google.gwt.event.dom.client.KeyUpEvent;
24 import com.google.gwt.event.dom.client.KeyUpHandler;
25 import com.google.gwt.regexp.shared.RegExp;
26 import com.google.gwt.uibinder.client.UiBinder;
27 import com.google.gwt.uibinder.client.UiField;
28 import com.google.gwt.user.client.Command;
29 import com.google.gwt.user.client.ui.Composite;
30 import com.google.gwt.user.client.ui.HTMLPanel;
31 import com.google.gwt.user.client.ui.Label;
32 import com.google.gwt.user.client.ui.TextBox;
33 import com.google.gwt.user.client.ui.Widget;
34 
35 public class AppNameTextbox extends Composite
36 {
37 
38    private static AppNameTextboxUiBinder uiBinder = GWT
39          .create(AppNameTextboxUiBinder.class);
40 
41    interface AppNameTextboxUiBinder extends UiBinder<Widget, AppNameTextbox>
42    {
43    }
44 
45    interface Host
46    {
supportsTitle()47       boolean supportsTitle();
generateAppName(String title, CommandWithArg<RSConnectAppName> result)48       void generateAppName(String title,
49                            CommandWithArg<RSConnectAppName> result);
50    }
51 
AppNameTextbox(Host host)52    public AppNameTextbox(Host host)
53    {
54       host_ = host;
55       initWidget(uiBinder.createAndBindUi(this));
56 
57       // Validate the application name on every keystroke
58       appTitle_.addKeyUpHandler(new KeyUpHandler()
59       {
60          @Override
61          public void onKeyUp(KeyUpEvent event)
62          {
63             validateAppName();
64          }
65       });
66    }
67 
setOnNameIsValid(Command cmd)68    public void setOnNameIsValid(Command cmd)
69    {
70       onNameIsValid_ = cmd;
71    }
72 
setOnNameIsInvalid(Command cmd)73    public void setOnNameIsInvalid(Command cmd)
74    {
75       onNameIsInvalidTitle_ = cmd;
76    }
77 
setTitle(String text)78    public void setTitle(String text)
79    {
80       appTitle_.setText(text);
81    }
82 
getTitle()83    public String getTitle()
84    {
85       return appTitle_.getText().trim();
86    }
87 
getTextBox()88    public TextBox getTextBox()
89    {
90       return appTitle_;
91    }
92 
getName()93    public String getName()
94    {
95       // return current title if no generated name is available
96       if (StringUtil.isNullOrEmpty(name_))
97          return getTitle();
98       return name_;
99    }
100 
setFocus(boolean focused)101    public void setFocus(boolean focused)
102    {
103       appTitle_.setFocus(focused);
104    }
105 
validateAppName()106    public void validateAppName()
107    {
108       if (!host_.supportsTitle())
109       {
110          String app = appTitle_.getText();
111          RegExp validReg = RegExp.compile("^[A-Za-z0-9_-]{4,63}$");
112          validTitle_ = validReg.test(app);
113          setAppNameValid(validTitle_);
114          if (validTitle_)
115             name_ = app;
116          else
117             error_.setText("The title must contain 4 - 64 alphanumeric " +
118                            "characters.");
119          return;
120       }
121 
122       // if we don't have enough characters, bail out early
123       final String title = appTitle_.getText().trim();
124       if (title.length() < 3)
125       {
126          validTitle_ = false;
127          // if we also don't have focus in the box, show an error
128          if (DomUtils.getActiveElement() != appTitle_.getElement())
129          {
130             setAppNameValid(false);
131             error_.setText("The title must contain at least 3 characters.");
132          }
133          return;
134       }
135 
136       host_.generateAppName(title,
137                             new CommandWithArg<RSConnectAppName>()
138          {
139             @Override
140             public void execute(RSConnectAppName arg)
141             {
142                name_ = arg.name();
143                validTitle_ = arg.valid();
144                error_.setText(arg.error());
145                setAppNameValid(arg.valid());
146             }
147          });
148    }
149 
150    @Override
setStyleName(String styleName)151    public void setStyleName(String styleName)
152    {
153       appTitle_.setStyleName(styleName);
154    }
155 
setDetails(String title, String name)156    public void setDetails(String title, String name)
157    {
158       if (StringUtil.isNullOrEmpty(title))
159          title = name;
160       appTitle_.setTitle(title);
161       name_ = name;
162    }
163 
isValid()164    public boolean isValid()
165    {
166       return !appTitle_.getText().trim().isEmpty() && validTitle_;
167    }
168 
169    // Private methods ---------------------------------------------------------
170 
setAppNameValid(boolean isValid)171    private void setAppNameValid(boolean isValid)
172    {
173       nameValidatePanel_.setVisible(!isValid);
174       if (isValid && onNameIsValid_ != null)
175          onNameIsValid_.execute();
176       else if (!isValid && onNameIsInvalidTitle_ != null)
177          onNameIsInvalidTitle_.execute();
178    }
179 
180    private final Host host_;
181    private Command onNameIsValid_;
182    private Command onNameIsInvalidTitle_;
183    private String name_;
184    private boolean validTitle_ = true;
185 
186    @UiField TextBox appTitle_;
187    @UiField HTMLPanel nameValidatePanel_;
188    @UiField Label error_;
189 }
190