Skip to content

Commit

Permalink
feat: Show all candidates in UI
Browse files Browse the repository at this point in the history
  • Loading branch information
jagodevreede committed Aug 26, 2024
1 parent 1ce6f06 commit 8a22833
Show file tree
Hide file tree
Showing 9 changed files with 239 additions and 78 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import io.github.jagodevreede.sdkman.api.files.ZipExtractTask;
import io.github.jagodevreede.sdkman.api.http.CachedHttpClient;
import io.github.jagodevreede.sdkman.api.http.DownloadTask;
import io.github.jagodevreede.sdkman.api.parser.CandidateListParser;
import io.github.jagodevreede.sdkman.api.parser.VersionListParser;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand All @@ -25,6 +26,9 @@
import java.util.Map;
import java.util.Objects;
import java.util.Scanner;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Stream;
Expand All @@ -47,7 +51,6 @@ public class SdkManApi {
* Used to extract name and dist from a identifier, first group is the name, second group is the dist
*/
private static final Pattern IDENTIFIER_PATTERN = Pattern.compile("(.*)-(.+)");

private final CachedHttpClient client;
private final String baseFolder;
private final String httpCacheFolder;
Expand Down Expand Up @@ -77,13 +80,15 @@ public String getHttpCacheFolder() {
return httpCacheFolder;
}

public List<Candidate> getCandidates() throws IOException, InterruptedException {
//String response = client.get(BASE_URL + "/candidates", offline);
//return CandidateListParser.parse(response);
// Use hard coded list for now as we don't support everything yet
return List.of(
new Candidate("java", "Java", "Java Development Kit"), new Candidate("maven", "Maven", "Maven")
);
public Future<List<Candidate>> getCandidates() {
return CompletableFuture.supplyAsync(() -> {
try {
String response = client.get(BASE_URL + "/candidates/list", offline);
return CandidateListParser.parse(response);
} catch (IOException | InterruptedException e) {
throw new RuntimeException(e);
}
});
}

public List<CandidateVersion> getVersions(String candidate) throws IOException, InterruptedException {
Expand All @@ -105,7 +110,11 @@ public List<CandidateVersion> getVersions(String candidate) throws IOException,
var available = localAvailable.remove(identifier);
if (matcher.matches()) {
var dist = matcher.group(2);
var name = vendors.stream().filter(v -> Objects.equals(v.dist(), dist)).findFirst().map(Vendor::vendor).orElse("Unclassified");
var name = vendors.stream()
.filter(v -> Objects.equals(v.dist(), dist))
.findFirst()
.map(Vendor::vendor)
.orElse("Unclassified");
result.add(new CandidateVersion(name, matcher.group(1), dist, identifier, true, available));
} else {
result.add(new CandidateVersion("Unclassified", "", "none", identifier, true, available));
Expand All @@ -116,7 +125,11 @@ public List<CandidateVersion> getVersions(String candidate) throws IOException,
Matcher matcher = IDENTIFIER_PATTERN.matcher(identifier);
if (matcher.matches()) {
var dist = matcher.group(2);
var name = vendors.stream().filter(v -> Objects.equals(v.dist(), dist)).findFirst().map(Vendor::vendor).orElse("Unclassified");
var name = vendors.stream()
.filter(v -> Objects.equals(v.dist(), dist))
.findFirst()
.map(Vendor::vendor)
.orElse("Unclassified");
result.add(new CandidateVersion(name, matcher.group(1), dist, identifier, false, true));
} else {
result.add(new CandidateVersion("Unclassified", "", "none", identifier, false, true));
Expand Down Expand Up @@ -173,7 +186,9 @@ private List<String> getLocalAvailableVersions(String candidate) {
if (!archiveFolder.exists()) {
return List.of();
}
return Stream.of(Objects.requireNonNull(archiveFolder.list((dir, name) -> new File(dir, name).isFile() && name.startsWith(candidate) && name.endsWith(".zip")))).map(name -> name.substring(candidate.length() + 1, name.length() - 4)).toList();
return Stream.of(Objects.requireNonNull(archiveFolder.list((dir, name) -> new File(dir, name).isFile() && name.startsWith(candidate) && name.endsWith(".zip"))))
.map(name -> name.substring(candidate.length() + 1, name.length() - 4))
.toList();
}

public String getCurrentCandidateFromPath(String candidate) {
Expand Down Expand Up @@ -406,7 +421,7 @@ public void initEnv() {
logger.debug("Initializing .sdkmanrc");
try (var writer = Files.newBufferedWriter(envFile.toPath())) {
writer.write("# Add key=value pairs of SDKs to use below" + newLine);
List<Candidate> candidates = getCandidates();
List<Candidate> candidates = getCandidates().get();
for (Candidate candidate : candidates) {
String currentCandidateFromPath = getCurrentCandidateFromPath(candidate.id());
if (currentCandidateFromPath != null) {
Expand All @@ -416,7 +431,7 @@ public void initEnv() {
writer.write(candidate.id() + "=" + currentCandidateFromPath + newLine);
}
}
} catch (IOException | InterruptedException e) {
} catch (IOException | InterruptedException | ExecutionException e) {
throw new RuntimeException(e);
}
}
Expand All @@ -429,4 +444,14 @@ public void clearEnv() {
exitMessages.add(".sdkmanrc not found");
}
}

public List<String> getLocalInstalledCandidates() {
var archiveFolder = new File(baseFolder + separator + "candidates");
if (!archiveFolder.exists()) {
return List.of();
}
return Stream.of(Objects.requireNonNull(archiveFolder.list((dir, name) -> new File(dir, name).isDirectory())))
.sorted()
.toList();
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package io.github.jagodevreede.sdkmanui;

import io.github.jagodevreede.sdkman.api.OsHelper;
import io.github.jagodevreede.sdkman.api.domain.Candidate;
import io.github.jagodevreede.sdkmanui.controller.MainScreenController;
import io.github.jagodevreede.sdkmanui.service.GlobalExceptionHandler;
import io.github.jagodevreede.sdkmanui.service.ServiceRegistry;
Expand All @@ -24,6 +25,7 @@
import java.nio.file.StandardCopyOption;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.Future;

import static io.github.jagodevreede.sdkmanui.view.Images.appIcon;

Expand All @@ -46,15 +48,17 @@ public void start(Stage stage) throws Exception {
return;
}
SERVICE_REGISTRY.getApi().registerShutdownHook();

if (handleArguments(list)) {
Platform.exit();
return;
}
Future<List<Candidate>> futureCandidates = SERVICE_REGISTRY.getApi().getCandidates();
setApplicationIconImage(stage);
loadServiceRegistry();

MainScreenController.getInstance();
MainScreenController mainScreenController = MainScreenController.getInstance();
List<Candidate> candidateList = futureCandidates.get();
mainScreenController.setCandidates(candidateList);

checkInstalled();
new UpdateChecker().checkForUpdate();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,27 @@
import io.github.jagodevreede.sdkman.api.ProgressInformation;
import io.github.jagodevreede.sdkman.api.SdkManApi;
import io.github.jagodevreede.sdkman.api.SdkManUiPreferences;
import io.github.jagodevreede.sdkman.api.domain.Candidate;
import io.github.jagodevreede.sdkman.api.domain.CandidateVersion;
import io.github.jagodevreede.sdkman.api.http.DownloadTask;
import io.github.jagodevreede.sdkmanui.ApplicationVersion;
import io.github.jagodevreede.sdkmanui.Main;
import io.github.jagodevreede.sdkmanui.service.ServiceRegistry;
import io.github.jagodevreede.sdkmanui.service.TaskRunner;
import io.github.jagodevreede.sdkmanui.view.Images;
import io.github.jagodevreede.sdkmanui.view.PopupView;
import io.github.jagodevreede.sdkmanui.view.VersionView;
import javafx.animation.PauseTransition;
import javafx.application.Platform;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.EventHandler;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.fxml.Initializable;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Cursor;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.CheckBox;
Expand All @@ -28,20 +34,30 @@
import javafx.scene.control.TableView;
import javafx.scene.control.TextField;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.stage.Modality;
import javafx.stage.Stage;
import javafx.util.Duration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.ResourceBundle;
import java.util.regex.Pattern;

import static io.github.jagodevreede.sdkmanui.view.Images.IMAGES_DIRECTORY;
import static io.github.jagodevreede.sdkmanui.view.Images.appIcon;
import static io.github.jagodevreede.sdkmanui.view.Images.defaultCandidateIcon;

public class MainScreenController implements Initializable {
private static MainScreenController INSTANCE = getInstance();
Expand All @@ -66,6 +82,8 @@ public class MainScreenController implements Initializable {
Pane updatePane;
@FXML
Label updateLabel;
@FXML
AnchorPane candidateListPane;

private final PauseTransition searchFieldPause = new PauseTransition(Duration.millis(300));
private ObservableList<VersionView> tableData;
Expand All @@ -81,6 +99,7 @@ public void initialize(URL url, ResourceBundle resourceBundle) {
final SdkManUiPreferences sdkManUiPreferences = ServiceRegistry.INSTANCE.getSdkManUiPreferences();
showInstalledOnly.setSelected(sdkManUiPreferences.showInstalled);
showAvailableOnly.setSelected(sdkManUiPreferences.showAvailable);
table.setPlaceholder(new Label("No versions found"));
table.getColumns().clear();

javaSelected();
Expand Down Expand Up @@ -113,22 +132,36 @@ public void loadData(Runnable onLoaded) {
String pathVersionInUse = api.getCurrentCandidateFromPath(selectedCandidate);
setGlobalVersionLabel(globalVersionInUse);
setPathVersionLabel(pathVersionInUse);
List<CandidateVersion> updatedVersions = api.getVersions(selectedCandidate).stream().filter(j -> !showInstalledOnly.isSelected() || j.installed()).filter(j -> !showAvailableOnly.isSelected() || j.available()).filter(j -> {
if (searchField == null || searchField.getText() == null || searchField.getText().isBlank()) {
return true;
} else {
final boolean vendorMatchesSearch = Pattern.compile(Pattern.quote(searchField.getText()), Pattern.CASE_INSENSITIVE).matcher(j.vendor()).find();
final boolean identifierMatchesSearch = Pattern.compile(Pattern.quote(searchField.getText()), Pattern.CASE_INSENSITIVE).matcher(j.identifier()).find();
return vendorMatchesSearch || identifierMatchesSearch;
}
}).toList();
List<CandidateVersion> updatedVersions = api.getVersions(selectedCandidate)
.stream()
.filter(j -> !showInstalledOnly.isSelected() || j.installed())
.filter(j -> !showAvailableOnly.isSelected() || j.available())
.filter(j -> {
if (searchField == null || searchField.getText() == null || searchField.getText()
.isBlank()) {
return true;
} else {
final boolean vendorMatchesSearch = Pattern.compile(Pattern.quote(searchField.getText()), Pattern.CASE_INSENSITIVE)
.matcher(j.vendor())
.find();
final boolean identifierMatchesSearch = Pattern.compile(Pattern.quote(searchField.getText()), Pattern.CASE_INSENSITIVE)
.matcher(j.identifier())
.find();
return vendorMatchesSearch || identifierMatchesSearch;
}
})
.toList();
Platform.runLater(() -> {
if (tableData == null || tableData.size() != updatedVersions.size()) {
tableData = FXCollections.observableArrayList(updatedVersions.stream().map(j -> new VersionView(j, globalVersionInUse, pathVersionInUse, thiz)).toList());
tableData = FXCollections.observableArrayList(updatedVersions.stream()
.map(j -> new VersionView(j, globalVersionInUse, pathVersionInUse, thiz))
.toList());
table.setItems(tableData);
} else {
tableData.forEach(oldData -> {
var found = updatedVersions.stream().filter(j -> j.identifier().equals(oldData.getIdentifier())).findFirst();
var found = updatedVersions.stream()
.filter(j -> j.identifier().equals(oldData.getIdentifier()))
.findFirst();
if (found.isPresent()) {
oldData.update(found.get(), globalVersionInUse, pathVersionInUse);
} else {
Expand Down Expand Up @@ -299,6 +332,73 @@ public void setUpdateAvailable(String latestRelease) {
});
}

public void setCandidates(List<Candidate> candidateList) {
List<Candidate> candidates = new ArrayList<>(candidateList);
final double prefHeight = 30.0;
final double prefPadding = 5.0;
int count = 0;
candidateListPane.setPrefHeight((prefHeight + prefPadding) * candidateList.size() + 5.0);
candidateListPane.getChildren().clear();
List<String> localInstalledCandidates = api.getLocalInstalledCandidates();
for (String candidateId : localInstalledCandidates) {
Candidate candidate = candidates.stream()
.filter(c -> c.id().equals(candidateId))
.findFirst()
.orElse(new Candidate(candidateId, candidateId, ""));
addCandateToScrollPane(candidate, count, prefHeight, prefPadding);
count++;
}

for (Candidate candidate : candidates) {
if (localInstalledCandidates.contains(candidate.id())) {
// We already added it
continue;
}
addCandateToScrollPane(candidate, count, prefHeight, prefPadding);
count++;
}
}

private void addCandateToScrollPane(Candidate candidate, int count, double prefHeight, double prefPadding) {
HBox hBox = new HBox();
hBox.setAlignment(Pos.CENTER_LEFT);
hBox.setLayoutX(4.0);
hBox.setLayoutY(count * (prefHeight + prefPadding));
hBox.setPrefHeight(prefHeight);
hBox.setPrefWidth(175.0);
hBox.getStyleClass().add("sidebar-button");
hBox.setCursor(Cursor.HAND);
hBox.setPadding(new Insets(5.0));
ImageView imageView = new ImageView();
imageView.setFitHeight(18.0);
imageView.setFitWidth(18.0);
imageView.setPickOnBounds(true);
imageView.setPreserveRatio(true);
InputStream imageResourceAsStream = Images.class.getResourceAsStream(IMAGES_DIRECTORY + "candidates/" + candidate.id()+ ".png");
if (imageResourceAsStream == null) {
imageView.setImage(defaultCandidateIcon);
} else {
imageView.setImage(new Image(imageResourceAsStream));
}

hBox.getChildren().add(imageView);
Label label = new Label();
label.setPrefHeight(17.0);
label.setPrefWidth(115.0);
label.setTranslateX(5.0);
label.setStyle("-fx-text-fill: white;");
label.setText(candidate.name());
label.setTextFill(Color.WHITE);
hBox.getChildren().add(label);

EventHandler<MouseEvent> mouseEventConsumer = (MouseEvent event) -> {
switchCandidate(candidate.id());
event.consume();
};
hBox.setOnMouseClicked(mouseEventConsumer);
candidateListPane.getChildren().add(hBox);
}

public static synchronized MainScreenController getInstance() {
if (INSTANCE == null) {
try {
Expand All @@ -322,5 +422,4 @@ public static synchronized MainScreenController getInstance() {
}
return INSTANCE;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,15 @@
import java.util.Objects;

public final class Images {
private static final String IMAGES_DIRECTORY = "/images/";
public static final String IMAGES_DIRECTORY = "/images/";

public static final Image appIcon = new Image(Objects.requireNonNull(Images.class.getResourceAsStream(IMAGES_DIRECTORY + "sdkman_ui_logo.png")));
public static final Image globalIcon = new Image(Objects.requireNonNull(Images.class.getResourceAsStream(IMAGES_DIRECTORY + "global_icon.png")));
public static final Image useIcon = new Image(Objects.requireNonNull(Images.class.getResourceAsStream(IMAGES_DIRECTORY + "use_icon.png")));
public static final Image checkIcon = new Image(Objects.requireNonNull(Images.class.getResourceAsStream(IMAGES_DIRECTORY + "check.png")));
public static final Image installIcon = new Image(Objects.requireNonNull(Images.class.getResourceAsStream(IMAGES_DIRECTORY + "install.png")));
public static final Image removeIcon = new Image(Objects.requireNonNull(Images.class.getResourceAsStream(IMAGES_DIRECTORY + "remove.png")));
public static final Image defaultCandidateIcon = new Image(Objects.requireNonNull(Images.class.getResourceAsStream(IMAGES_DIRECTORY + "candidates/default.png")));

private Images() {
// Private constructor, because this is a constrains class
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit 8a22833

Please sign in to comment.