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