1
+ package com .microsoft .azure .toolkit .intellij .explorer .azd ;
2
+
3
+ import com .fasterxml .jackson .annotation .JsonInclude ;
4
+ import com .fasterxml .jackson .core .type .TypeReference ;
5
+ import com .fasterxml .jackson .databind .DeserializationFeature ;
6
+ import com .fasterxml .jackson .databind .ObjectMapper ;
7
+ import com .intellij .icons .AllIcons ;
8
+ import com .intellij .ide .BrowserUtil ;
9
+ import com .intellij .openapi .application .ApplicationManager ;
10
+ import com .intellij .openapi .progress .ProgressIndicator ;
11
+ import com .intellij .openapi .progress .ProgressManager ;
12
+ import com .intellij .openapi .progress .Task ;
13
+ import com .intellij .openapi .project .Project ;
14
+ import com .intellij .openapi .ui .DialogWrapper ;
15
+ import com .intellij .openapi .ui .VerticalFlowLayout ;
16
+ import com .intellij .openapi .ui .popup .JBPopup ;
17
+ import com .intellij .ui .HyperlinkLabel ;
18
+ import com .intellij .ui .ScrollPaneFactory ;
19
+ import com .intellij .ui .components .JBCheckBox ;
20
+ import com .intellij .ui .components .JBLabel ;
21
+ import com .intellij .ui .components .JBPanel ;
22
+ import com .intellij .util .ui .JBUI ;
23
+ import com .microsoft .azure .toolkit .intellij .common .AzureActionButton ;
24
+ import com .microsoft .azure .toolkit .intellij .common .TerminalUtils ;
25
+ import org .jdesktop .swingx .HorizontalLayout ;
26
+ import org .jetbrains .annotations .NotNull ;
27
+ import org .jetbrains .annotations .Nullable ;
28
+
29
+ import javax .swing .*;
30
+ import javax .swing .event .HyperlinkEvent ;
31
+ import java .awt .*;
32
+ import java .io .IOException ;
33
+ import java .net .URL ;
34
+ import java .util .ArrayList ;
35
+ import java .util .Collections ;
36
+ import java .util .HashMap ;
37
+ import java .util .List ;
38
+ import java .util .Map ;
39
+ import java .util .stream .Collectors ;
40
+
41
+ public class AzdAvailableTemplatesPopup extends JPanel {
42
+ private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper ()
43
+ .setSerializationInclusion (JsonInclude .Include .NON_NULL )
44
+ .configure (DeserializationFeature .FAIL_ON_UNKNOWN_PROPERTIES , false );
45
+ private final Color activeColor = new Color (100 , 150 , 255 );
46
+ private final Color inactiveColor = Color .GRAY ;
47
+ private final Project project ;
48
+ private List <AzdTemplate > templates = new ArrayList <>();
49
+ private List <JBCheckBox > tagButtons = new ArrayList <>();
50
+ private JBPopup popup ;
51
+
52
+ public AzdAvailableTemplatesPopup (Project project ) {
53
+ this .project = project ;
54
+ setLayout (new BorderLayout ());
55
+ setBorder (JBUI .Borders .empty (10 ));
56
+ this .templates = readFromGitHub ("https://raw.githubusercontent.com/Azure/awesome-azd/refs/heads/main/website/static/templates.json" );
57
+
58
+ final List <String > allTags = topKTags (templates , 10 );
59
+
60
+ final JPanel tilesPanel = new JPanel ();
61
+ final JPanel filterTagsPanel = new JPanel (new HorizontalLayout (JBUI .scale (10 )));
62
+ filterTagsPanel .setBorder (JBUI .Borders .emptyBottom (10 )); // Adds 10px space below the panel
63
+ final JBLabel filterLabel = new JBLabel ("Filter by tags:" );
64
+ filterTagsPanel .add (filterLabel );
65
+
66
+ // Create scroll pane
67
+ addFilters (filterTagsPanel , tilesPanel , allTags );
68
+
69
+ final JScrollPane tagsScrollPane = ScrollPaneFactory .createScrollPane (filterTagsPanel );
70
+ add (tagsScrollPane , BorderLayout .NORTH );
71
+
72
+ // Create a scroll pane for the tiles
73
+ tilesPanel .setLayout (new VerticalFlowLayout (VerticalFlowLayout .MIDDLE , true , true ));
74
+
75
+ tilesPanel .setBorder (JBUI .Borders .empty (10 ));
76
+
77
+ // Add scroll pane with tiles panel
78
+ final JScrollPane scrollPane = ScrollPaneFactory .createScrollPane (tilesPanel );
79
+ scrollPane .setBorder (JBUI .Borders .empty ());
80
+ add (scrollPane , BorderLayout .CENTER );
81
+
82
+
83
+ // Load data
84
+ loadData (tilesPanel , Collections .emptyList ());
85
+ }
86
+
87
+ public static List <AzdTemplate > readFromGitHub (String githubUrl ) {
88
+ try {
89
+ // Read JSON from URL directly into the Repository model
90
+ return OBJECT_MAPPER .readValue (new URL (githubUrl ), new TypeReference <List <AzdTemplate >>() {});
91
+ } catch (IOException e ) {
92
+ System .err .println ("Error reading JSON from GitHub URL: " + e .getMessage ());
93
+ return null ;
94
+ }
95
+ }
96
+
97
+ private void addFilters (JPanel filterTagsPanel , JPanel tilesPanel , List <String > allTags ) {
98
+ allTags .forEach (tag -> {
99
+ final JBCheckBox tagCheckbox = new JBCheckBox (tag );
100
+ tagCheckbox .addActionListener (e -> {
101
+ final List <String > selectedTags = tagButtons .stream ()
102
+ .filter (AbstractButton ::isSelected )
103
+ .map (AbstractButton ::getText )
104
+ .collect (Collectors .toList ());
105
+
106
+ loadData (tilesPanel , selectedTags );
107
+ });
108
+ filterTagsPanel .add (tagCheckbox );
109
+ tagButtons .add (tagCheckbox );
110
+ });
111
+ }
112
+
113
+ /**
114
+ * Load data from command execution and create tiles
115
+ */
116
+ private void loadData (JPanel tilesPanel , List <String > tags ) {
117
+ ProgressManager .getInstance ().run (new Task .Backgroundable (project , "Loading Tool Data" , false ) {
118
+ @ Override
119
+ public void run (@ NotNull ProgressIndicator indicator ) {
120
+ indicator .setText ("Executing command to load templates..." );
121
+ final List <AzdTemplate > javaTemplates = templates .stream ()
122
+ .filter (template -> template .getLanguages () != null && template .getLanguages ().contains ("java" ))
123
+ .toList ();
124
+
125
+ final List <AzdTemplate > tagTemplates = javaTemplates .stream ()
126
+ .filter (template -> tags == null || tags .isEmpty () || template .getTags ().containsAll (tags ))
127
+ .toList ();
128
+
129
+ ApplicationManager .getApplication ().invokeLater (() -> {
130
+ tilesPanel .removeAll ();
131
+
132
+ for (final AzdTemplate item : tagTemplates ) {
133
+ createToolTile (item , tilesPanel );
134
+ }
135
+
136
+ tilesPanel .revalidate ();
137
+ tilesPanel .repaint ();
138
+ });
139
+ }
140
+ });
141
+ }
142
+
143
+ private void createToolTile (AzdTemplate item , JPanel tilesPanel ) {
144
+ // Title at the top
145
+ final JBLabel titleLabel = new JBLabel (item .getTitle ());
146
+ titleLabel .setFont (titleLabel .getFont ().deriveFont (Font .BOLD , titleLabel .getFont ().getSize () + 2 ));
147
+ titleLabel .setBorder (JBUI .Borders .empty (0 , 0 , 3 , 0 ));
148
+ tilesPanel .add (titleLabel );
149
+
150
+ final JBLabel label = new JBLabel ("<html><p style='width: 900px;'>" + item .getDescription () + "<p></html>" );
151
+ tilesPanel .add (label );
152
+
153
+ final HyperlinkLabel githubLink = new HyperlinkLabel (item .getAuthorUrl ());
154
+ githubLink .addHyperlinkListener (e -> BrowserUtil .browse (((HyperlinkLabel ) ((HyperlinkEvent ) e ).getSource ()).getText ()));
155
+ githubLink .setBorder (JBUI .Borders .empty (2 , 0 , 3 , 0 ));
156
+
157
+ tilesPanel .add (githubLink );
158
+
159
+ final JBPanel commandPanel = new JBPanel (new HorizontalLayout (JBUI .scale (5 )));
160
+ commandPanel .setBorder (JBUI .Borders .emptyBottom (15 ));
161
+ commandPanel .add (new JLabel ((AllIcons .Debugger .Console )));
162
+
163
+ final JBLabel commandLabel = new JBLabel ("Command: " );
164
+ commandPanel .add (commandLabel );
165
+ final JTextField textBox = new JTextField (100 );
166
+ textBox .setMaximumSize (new Dimension (200 , 30 ));
167
+ textBox .setText ("azd init -t " + item .getSource ());
168
+
169
+ textBox .setFont (textBox .getFont ().deriveFont (Font .TRUETYPE_FONT , textBox .getFont ().getSize ()));
170
+ commandPanel .add (textBox );
171
+
172
+ final AzureActionButton <Void > runButton = new AzureActionButton <>();
173
+ runButton .setText ("Run" );
174
+ runButton .requestFocusInWindow ();
175
+ runButton .addActionListener (e -> {
176
+ final String command = textBox .getText ();
177
+ if (command .isEmpty ()) {
178
+ JOptionPane .showMessageDialog (this , "Command cannot be empty" , "Error" , JOptionPane .ERROR_MESSAGE );
179
+ return ;
180
+ }
181
+ // Show confirmation dialog
182
+ RunConfirmationDialog dialog = new RunConfirmationDialog (command );
183
+ dialog .setTitle ("Confirm Run" );
184
+ dialog .show ();
185
+ });
186
+ commandPanel .add (runButton );
187
+
188
+ tilesPanel .add (commandPanel );
189
+
190
+ final JSeparator separator = new JSeparator (SwingConstants .HORIZONTAL );
191
+ tilesPanel .add (separator );
192
+ }
193
+
194
+ /**
195
+ * Dialog to show confirmation for running a command
196
+ */
197
+ private class RunConfirmationDialog extends DialogWrapper {
198
+ private final String command ;
199
+
200
+ public RunConfirmationDialog (String command ) {
201
+ super (project , false );
202
+ this .command = command ;
203
+ setTitle ("Confirm Run" );
204
+ init ();
205
+ }
206
+
207
+ @ Override
208
+ protected @ Nullable JComponent createCenterPanel () {
209
+ final JPanel panel = new JPanel (new BorderLayout ());
210
+ panel .add (new JLabel ("Run command: " + command + "?" ), BorderLayout .CENTER );
211
+ return panel ;
212
+ }
213
+ @ Override
214
+ protected void doOKAction () {
215
+ super .doOKAction ();
216
+ TerminalUtils .executeInTerminal (project , command , "azd" );
217
+ }
218
+ }
219
+
220
+ public void setPopup (JBPopup popup ) {
221
+ this .popup = popup ;
222
+ }
223
+
224
+
225
+ public static List <String > topKTags (List <AzdTemplate > input , int k ) {
226
+ // Count occurrences
227
+ final Map <String , Integer > frequencyMap = new HashMap <>();
228
+ final List <String > tags = input .stream ()
229
+ .filter (template -> template .getLanguages () != null && template .getLanguages ().contains ("java" ))
230
+ .flatMap (template -> template .getTags ().stream ())
231
+ .toList ();
232
+
233
+ for (final String str : tags ) {
234
+ frequencyMap .put (str , frequencyMap .getOrDefault (str , 0 ) + 1 );
235
+ }
236
+
237
+ // Sort by frequency in descending order
238
+ return frequencyMap .entrySet ().stream ()
239
+ .sorted (Map .Entry .<String , Integer >comparingByValue ().reversed ())
240
+ .limit (k )
241
+ .map (Map .Entry ::getKey )
242
+ .collect (Collectors .toList ());
243
+ }
244
+ }
0 commit comments