@@ -38,6 +38,9 @@ module App
3838 @@search_id : Int64 = 0
3939 @@detected_products : Array (JBUpdater ::DetectedProduct )? = nil
4040 @@installed_plugins : Hash (String , JBUpdater ::PluginMeta )? = nil
41+ @@installed_plugins_arr : Array (JBUpdater ::PluginMeta ) = [] of JBUpdater ::PluginMeta
42+ @@installed_table : UIng ::Table ? = nil
43+ @@installed_model : UIng ::Table ::Model ? = nil
4144
4245 # Background operation state
4346 @@op_status : String = " "
@@ -162,6 +165,35 @@ module App
162165
163166 def self.installed_plugins = (plugins : Hash (String , JBUpdater ::PluginMeta )?)
164167 @@installed_plugins = plugins
168+ refresh_installed_list
169+ end
170+
171+ def self.installed_plugins_arr
172+ @@installed_plugins_arr
173+ end
174+
175+ def self.refresh_installed_list
176+ if hash = @@installed_plugins
177+ @@installed_plugins_arr = hash.values.sort_by!(& .id)
178+ else
179+ @@installed_plugins_arr = [] of JBUpdater ::PluginMeta
180+ end
181+ end
182+
183+ def self.installed_table
184+ @@installed_table
185+ end
186+
187+ def self.installed_table = (table : UIng ::Table ?)
188+ @@installed_table = table
189+ end
190+
191+ def self.installed_model
192+ @@installed_model
193+ end
194+
195+ def self.installed_model = (model : UIng ::Table ::Model ?)
196+ @@installed_model = model
165197 end
166198
167199 def self.browse_detail
@@ -866,6 +898,117 @@ UIng.init do
866898
867899 tabs.append(" Browse" , browse_tab)
868900
901+ # --- Installed tab ---------------------------------------------------
902+ installed_tab = UIng ::Box .new(:vertical )
903+ installed_tab.padded = true
904+
905+ installed_handler = UIng ::Table ::Model ::Handler .new do
906+ num_columns { 5 }
907+ column_type { |_col | UIng ::Table ::Value ::Type ::String }
908+ num_rows { App .installed_plugins_arr.size }
909+ cell_value { |row , col |
910+ plugin = App .installed_plugins_arr[row]?
911+ next UIng ::Table ::Value .new(" " ) unless plugin
912+ case col
913+ when 0 then UIng ::Table ::Value .new(plugin.name || plugin.id)
914+ when 1 then UIng ::Table ::Value .new(plugin.id)
915+ when 2 then UIng ::Table ::Value .new(plugin.version)
916+ when 3 then UIng ::Table ::Value .new(plugin.since || " —" )
917+ else UIng ::Table ::Value .new(plugin.until_build || " —" )
918+ end
919+ }
920+ end
921+ installed_model = UIng ::Table ::Model .new(installed_handler)
922+ App .installed_table = installed_table = UIng ::Table .new(installed_model)
923+ App .installed_model = installed_model
924+ installed_table.header_visible = true
925+ installed_table.append_text_column(" Name" , 0 , -1 )
926+ installed_table.append_text_column(" Plugin ID" , 1 , -1 )
927+ installed_table.append_text_column(" Version" , 2 , -1 )
928+ installed_table.append_text_column(" Since Build" , 3 , -1 )
929+ installed_table.append_text_column(" Until Build" , 4 , -1 )
930+ installed_table.selection_mode = :one
931+
932+ installed_actions = UIng ::Box .new(:horizontal )
933+ installed_actions.padded = true
934+
935+ btn_scan_installed = UIng ::Button .new(" Scan" )
936+ btn_uninstall = UIng ::Button .new(" Uninstall selected" )
937+ btn_uninstall.disable
938+
939+ installed_actions.append(btn_scan_installed, false )
940+ installed_actions.append(btn_uninstall, false )
941+
942+ installed_status = UIng ::Label .new(" Click Scan to list installed plugins" )
943+
944+ installed_tab.append(installed_actions, false )
945+ installed_tab.append(installed_table, true )
946+ installed_tab.append(installed_status, false )
947+
948+ tabs.append(" Installed" , installed_tab)
949+
950+ btn_scan_installed.on_clicked do
951+ dir = expand_tilde(e_plugins_dir.text) || e_plugins_dir.text || " "
952+ if dir.empty?
953+ installed_status.text = " Set Plugins Directory first"
954+ next
955+ end
956+ scanned = JBUpdater ::PluginMeta .scan_dir(dir) rescue nil
957+ if scanned
958+ old_count = App .installed_plugins_arr.size
959+ App .installed_plugins = scanned
960+ if old_count == 0
961+ App .installed_plugins_arr.each_with_index { |_ , i | App .installed_model.try & .row_inserted(i) }
962+ else
963+ (0 ...[App .installed_plugins_arr.size, old_count].min).each { |i | App .installed_model.try & .row_changed(i) }
964+ if App .installed_plugins_arr.size > old_count
965+ (old_count...App .installed_plugins_arr.size).each { |i | App .installed_model.try & .row_inserted(i) }
966+ elsif App .installed_plugins_arr.size < old_count
967+ (App .installed_plugins_arr.size...old_count).reverse_each { |i | App .installed_model.try & .row_deleted(i) }
968+ end
969+ end
970+ installed_status.text = " Found #{ scanned.size } installed plugins"
971+ else
972+ installed_status.text = " Error scanning plugins directory"
973+ end
974+ end
975+
976+ installed_table.on_selection_changed do |selection |
977+ if selection.num_rows > 0
978+ btn_uninstall.enable
979+ else
980+ btn_uninstall.disable
981+ end
982+ end
983+
984+ btn_uninstall.on_clicked do
985+ UIng .queue_main do
986+ installed_table.selection do |sel |
987+ next if sel.num_rows == 0
988+ row = sel.rows[0 ]
989+ plugin = App .installed_plugins_arr[row]?
990+ if plugin
991+ FileUtils .rm_rf(plugin.path)
992+ scanned = JBUpdater ::PluginMeta .scan_dir(File .dirname(plugin.path)) rescue nil
993+ old_count = App .installed_plugins_arr.size
994+ App .installed_plugins = scanned
995+ if old_count == 0
996+ App .installed_plugins_arr.each_with_index { |_ , i | App .installed_model.try & .row_inserted(i) }
997+ else
998+ (0 ...[App .installed_plugins_arr.size, old_count].min).each { |i | App .installed_model.try & .row_changed(i) }
999+ if App .installed_plugins_arr.size > old_count
1000+ (old_count...App .installed_plugins_arr.size).each { |i | App .installed_model.try & .row_inserted(i) }
1001+ elsif App .installed_plugins_arr.size < old_count
1002+ (App .installed_plugins_arr.size...old_count).reverse_each { |i | App .installed_model.try & .row_deleted(i) }
1003+ end
1004+ end
1005+ installed_status.text = " Deleted: #{ plugin.id } "
1006+ btn_uninstall.disable
1007+ end
1008+ end
1009+ end
1010+ end
1011+
8691012 # --- IDE tab --------------------------------------------------------
8701013 ide_tab = UIng ::Box .new(:vertical )
8711014 ide_tab.padded = true
@@ -1092,6 +1235,12 @@ UIng.init do
10921235 }
10931236 load_installed_for_browse.call
10941237
1238+ # Populate installed tab model with startup data
1239+ inst = App .installed_plugins
1240+ if inst && inst.size > 0
1241+ App .installed_plugins_arr.each_with_index { |_ , i | App .installed_model.try & .row_inserted(i) }
1242+ end
1243+
10951244 # Warm marketplace cache after UI is visible (1s delay)
10961245 UIng .timer(1_000 ) {
10971246 build = resolve_build.call
@@ -1100,44 +1249,42 @@ UIng.init do
11001249 0
11011250 }
11021251
1103- search_entry.on_changed do |_text |
1104- UIng .queue_main do
1105- begin
1106- query = search_entry.text || " "
1107- if query.empty?
1108- model = App .browse_table_model
1109- if model
1110- old_count = App .browse_plugins.size
1111- (0...old_count ).each { |i | model.row_deleted(i) }
1112- end
1252+ search_entry.on_changed do |text |
1253+ begin
1254+ query = text || " "
1255+ if query.empty?
1256+ model = App .browse_table_model
1257+ if model
1258+ old_count = App .browse_plugins.size
11131259 App .browse_plugins = [] of JBUpdater ::PluginInfo
1114- App .selected_xml_id = nil
1115- browse_status.text = " Type to search plugins..."
1116- next
1260+ (0...old_count ).each { |i | model.row_deleted(0 ) }
11171261 end
1262+ App .selected_xml_id = nil
1263+ browse_status.text = " Type to search plugins..."
1264+ next
1265+ end
11181266
1119- build = resolve_build.call
1120- plugins = JBUpdater ::PluginMarketplace .search(query, build)
1121-
1122- model = App .browse_table_model
1123- next unless model
1267+ build = resolve_build.call
1268+ plugins = JBUpdater ::PluginMarketplace .search(query, build)
11241269
1125- old_count = App .browse_plugins.size
1126- App .browse_plugins = plugins
1127- if old_count == 0
1128- plugins.each_with_index { |_ , i | model.row_inserted(i) }
1129- elsif plugins.size >= old_count
1130- (0...old_count ).each { |i | model.row_changed(i) }
1131- (old_count...plugins.size).each { |i | model.row_inserted(i) }
1132- else
1133- (0 ...plugins.size).each { |i | model.row_changed(i) }
1134- (plugins.size...old_count).each { |i | model.row_deleted(i) }
1135- end
1136- browse_status.text = " Found #{ plugins.size } results"
1137- rescue ex
1138- log.append(" [Browse] Search error: #{ ex.class } : #{ ex.message } \n " )
1139- browse_status.text = " Search error: #{ ex.class } #{ ex.message } "
1270+ model = App .browse_table_model
1271+ next unless model
1272+
1273+ old_count = App .browse_plugins.size
1274+ App .browse_plugins = plugins
1275+ if old_count == 0
1276+ plugins.each_with_index { |_ , i | model.row_inserted(i) }
1277+ elsif plugins.size >= old_count
1278+ (0...old_count ).each { |i | model.row_changed(i) }
1279+ (old_count...plugins.size).each { |i | model.row_inserted(i) }
1280+ else
1281+ (0 ...plugins.size).each { |i | model.row_changed(i) }
1282+ (plugins.size...old_count).reverse_each { |i | model.row_deleted(i) }
11401283 end
1284+ browse_status.text = " Found #{ plugins.size } results"
1285+ rescue ex
1286+ log.append(" [Browse] Search error: #{ ex.class } : #{ ex.message } \n " )
1287+ browse_status.text = " Search error: #{ ex.class } #{ ex.message } "
11411288 end
11421289 end
11431290
@@ -1161,7 +1308,7 @@ UIng.init do
11611308 (old_count...plugins.size).each { |i | model.row_inserted(i) }
11621309 else
11631310 (0 ...plugins.size).each { |i | model.row_changed(i) }
1164- (plugins.size...old_count).each { |i | model.row_deleted(i) }
1311+ (plugins.size...old_count).reverse_each { |i | model.row_deleted(i) }
11651312 end
11661313 browse_status.text = " Loaded #{ plugins.size } plugins (top downloads)"
11671314 end
@@ -1211,7 +1358,7 @@ UIng.init do
12111358 plugin = row >= 0 ? App .browse_plugins[row]? : nil
12121359 if plugin
12131360 App .selected_xml_id = plugin.xml_id
1214- stripped = JBUpdater :: PluginMarketplace .html_strip( plugin.description)
1361+ stripped = plugin.description[ 0 , 500 ]
12151362 preview = stripped[0 , 500 ]
12161363 App .log.append(" [Browse] detail: #{ preview.size } B #{ preview.count('\n' ) } lines (#{ preview.size - preview.count('\n' ) } non-newline)\n " )
12171364 App .safe_set_text(browse_detail, preview)
0 commit comments