-
-
Notifications
You must be signed in to change notification settings - Fork 75
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Peter Mucha
committed
Feb 16, 2024
1 parent
068245f
commit 83ce5c5
Showing
9 changed files
with
321 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
84 changes: 84 additions & 0 deletions
84
...lugins-management/src/main/java/milkman/ui/plugins/management/market/GithubApiClient.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
package milkman.ui.plugins.management.market; | ||
|
||
import com.fasterxml.jackson.annotation.JsonProperty; | ||
import com.fasterxml.jackson.databind.DeserializationFeature; | ||
import com.fasterxml.jackson.databind.ObjectMapper; | ||
import java.io.IOException; | ||
import java.io.InputStream; | ||
import java.net.HttpURLConnection; | ||
import java.net.URL; | ||
import java.util.List; | ||
import lombok.SneakyThrows; | ||
|
||
public class GithubApiClient { | ||
|
||
|
||
private ObjectMapper mapper = new ObjectMapper(); | ||
|
||
public GithubApiClient() { | ||
mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES); | ||
} | ||
|
||
@SneakyThrows | ||
public List<MarketplacePlugin> fetchPluginRepos() { | ||
return searchForRepos("milkman-plugins") | ||
.items().stream() | ||
.flatMap(res -> loadPluginDescriptorForRepo(res.fullName()) | ||
.stream()) | ||
.toList(); | ||
} | ||
|
||
private GithubRepoSearchResultList searchForRepos(String topic) throws IOException { | ||
InputStream inputStream = new URL( | ||
"https://api.github.com/search/repositories?q=topic:" + topic + | ||
"&sort=created&order=asc").openStream(); | ||
GithubRepoSearchResultList searchResult = | ||
mapper.readValue(inputStream, GithubRepoSearchResultList.class); | ||
return searchResult; | ||
} | ||
|
||
@SneakyThrows | ||
private List<MarketplacePlugin> loadPluginDescriptorForRepo(String fullRepoName) { | ||
HttpURLConnection con = | ||
(HttpURLConnection) new URL("https://api.github.com/repos/" + fullRepoName + | ||
"/contents/milkman-plugin.json").openConnection(); | ||
con.setRequestProperty("Accept", "application/vnd.github.raw+json"); | ||
con.connect(); | ||
|
||
if (con.getResponseCode() != 200) { | ||
return List.of(); | ||
} | ||
|
||
GithubPluginRepoDescription result = | ||
mapper.readValue(con.getInputStream(), GithubPluginRepoDescription.class); | ||
|
||
|
||
return result.plugins().stream().map( | ||
p -> new MarketplacePlugin(p.author(), p.name(), p.description(), | ||
"https://github.com/" + fullRepoName + "/releases/latest/download/" + p.artifact(), | ||
"https:/github.com/" + fullRepoName) | ||
).toList(); | ||
} | ||
|
||
|
||
record GithubRepoSearchResultList( | ||
List<GithubRepoSearchResult> items | ||
) { | ||
|
||
} | ||
|
||
record GithubRepoSearchResult(@JsonProperty("full_name") String fullName) { | ||
|
||
} | ||
|
||
public record GithubPluginRepoDescription(List<GithubPluginDescription> plugins) { | ||
} | ||
|
||
public record GithubPluginDescription(String author, String name, String artifact, String description) { | ||
|
||
} | ||
|
||
public record MarketplacePlugin(String author, String name, String description, String artifactUrl, String documentationUrl) { | ||
} | ||
|
||
} |
170 changes: 170 additions & 0 deletions
170
...gins-management/src/main/java/milkman/ui/plugins/management/market/MarketplaceDialog.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,170 @@ | ||
package milkman.ui.plugins.management.market; | ||
|
||
import static milkman.utils.fxml.FxmlBuilder.button; | ||
import static milkman.utils.fxml.FxmlBuilder.cancel; | ||
import static milkman.utils.fxml.FxmlBuilder.hbox; | ||
import static milkman.utils.fxml.FxmlBuilder.icon; | ||
import static milkman.utils.fxml.FxmlBuilder.label; | ||
import static milkman.utils.fxml.FxmlBuilder.text; | ||
import static milkman.utils.fxml.FxmlBuilder.vbox; | ||
|
||
import com.jfoenix.controls.JFXButton; | ||
import com.jfoenix.controls.JFXDialogLayout; | ||
import com.jfoenix.controls.JFXListCell; | ||
import com.jfoenix.controls.JFXTextField; | ||
import de.jensd.fx.glyphs.fontawesome.FontAwesomeIcon; | ||
import java.time.Duration; | ||
import java.util.Comparator; | ||
import javafx.application.Platform; | ||
import javafx.collections.FXCollections; | ||
import javafx.collections.ObservableList; | ||
import javafx.collections.transformation.FilteredList; | ||
import javafx.collections.transformation.SortedList; | ||
import javafx.scene.Node; | ||
import javafx.scene.control.Dialog; | ||
import javafx.scene.control.Label; | ||
import javafx.scene.control.ListView; | ||
import javafx.scene.layout.Border; | ||
import javafx.scene.layout.BorderStroke; | ||
import javafx.scene.layout.BorderStrokeStyle; | ||
import javafx.scene.layout.BorderWidths; | ||
import javafx.scene.layout.HBox; | ||
import javafx.scene.layout.Priority; | ||
import javafx.scene.paint.Paint; | ||
import javafx.scene.text.Font; | ||
import javafx.scene.text.FontWeight; | ||
import lombok.Getter; | ||
import lombok.RequiredArgsConstructor; | ||
import milkman.PlatformUtil; | ||
import milkman.ui.plugins.management.market.GithubApiClient.MarketplacePlugin; | ||
import milkman.utils.fxml.FxmlBuilder; | ||
import milkman.utils.fxml.FxmlUtil; | ||
import org.reactfx.EventStreams; | ||
|
||
public class MarketplaceDialog { | ||
|
||
private JFXTextField txt_search; | ||
private GithubApiClient apiClient = new GithubApiClient(); | ||
private Dialog dialog; | ||
private ListView<MarketplacePlugin> pluginList; | ||
@Getter | ||
private MarketplacePlugin chosenPlugin; | ||
|
||
|
||
@Getter | ||
boolean cancelled = true; | ||
|
||
public MarketplaceDialog() { | ||
} | ||
|
||
public void showAndWait() { | ||
JFXDialogLayout content = new MarketplaceDialogFxml(this); | ||
|
||
pluginList.setCellFactory(view -> new MarketplacePluginCell()); | ||
pluginList.setMinWidth(600); | ||
pluginList.setBorder(new Border( | ||
new BorderStroke(Paint.valueOf("black"), BorderStrokeStyle.SOLID, null, | ||
new BorderWidths(1)))); | ||
ObservableList<MarketplacePlugin> observableList = | ||
FXCollections.observableArrayList(apiClient.fetchPluginRepos()); | ||
SortedList<MarketplacePlugin> sortedList = | ||
observableList.sorted(Comparator.comparing(MarketplacePlugin::name)); | ||
FilteredList<MarketplacePlugin> filteredList = new FilteredList<>(sortedList); | ||
|
||
|
||
EventStreams.nonNullValuesOf(txt_search.textProperty()) | ||
.successionEnds(Duration.ofMillis(250)) | ||
.subscribe(qry -> setFilterPredicate(filteredList, qry)); | ||
|
||
|
||
pluginList.setItems(filteredList); | ||
|
||
|
||
|
||
|
||
dialog = FxmlUtil.createDialog(content); | ||
dialog.showAndWait(); | ||
} | ||
|
||
private void setFilterPredicate(FilteredList<MarketplacePlugin> filteredList, String searchTerm) { | ||
if (searchTerm != null && !searchTerm.isEmpty()) { | ||
filteredList.setPredicate(p -> p.name().toLowerCase().contains(searchTerm.toLowerCase()) | ||
|| p.description().toLowerCase().contains(searchTerm.toLowerCase()) | ||
|| p.author().toLowerCase().contains(searchTerm.toLowerCase())); | ||
} else { | ||
filteredList.setPredicate(o -> true); | ||
} | ||
|
||
} | ||
|
||
private void onSave(MarketplacePlugin chosenPlugin) { | ||
cancelled = false; | ||
this.chosenPlugin = chosenPlugin; | ||
dialog.close(); | ||
} | ||
|
||
private void onCancel() { | ||
cancelled = true; | ||
dialog.close(); | ||
} | ||
|
||
public static class MarketplaceDialogFxml extends JFXDialogLayout { | ||
|
||
public MarketplaceDialogFxml(MarketplaceDialog controller) { | ||
setHeading(label("Plugin Marketplace")); | ||
|
||
setBody(vbox( | ||
controller.txt_search = text("plugin-search", "search for plugins..."), | ||
controller.pluginList = new ListView<>() | ||
) | ||
); | ||
|
||
setActions(cancel(controller::onCancel)); | ||
} | ||
} | ||
|
||
@RequiredArgsConstructor | ||
class MarketplacePluginCell extends JFXListCell<MarketplacePlugin> { | ||
@Override | ||
protected void updateItem(MarketplacePlugin plugin, boolean empty) { | ||
super.updateItem(plugin, empty); | ||
if (empty || plugin == null) { | ||
setText(null); | ||
setGraphic(null); | ||
} else { | ||
setText(null); | ||
setGraphic(createEntry(plugin)); | ||
} | ||
} | ||
|
||
private Node createEntry(MarketplacePlugin plugin) { | ||
Label title = label(plugin.name()); | ||
title.setFont(Font.font(title.getFont().getFamily(), FontWeight.EXTRA_BOLD, | ||
title.getFont().getSize() + 4)); | ||
|
||
Label label = label("Author: " + plugin.author()); | ||
label.setOpacity(0.7); | ||
|
||
Label desc = label(plugin.description()); | ||
desc.setMaxWidth(400); | ||
FxmlBuilder.VboxExt pluginDesc = vbox( | ||
title, | ||
desc, | ||
hbox(label) | ||
); | ||
HBox.setHgrow(pluginDesc, Priority.ALWAYS); | ||
|
||
JFXButton visitButton = button("marketplace.goto-plugin", icon(FontAwesomeIcon.GLOBE), () -> { | ||
PlatformUtil.tryOpenBrowser(plugin.documentationUrl()); | ||
} | ||
); | ||
JFXButton installButton = button("marketplace.install-plugin", icon(FontAwesomeIcon.DOWNLOAD), () -> { | ||
Platform.runLater(() -> onSave(plugin)); | ||
} | ||
); | ||
|
||
FxmlBuilder.HboxExt content = hbox(pluginDesc, vbox(visitButton, installButton)); | ||
return content; | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters