Skip to content

Commit 64cc839

Browse files
authored
Merge pull request #503 from DropSnorz/feat/package-filters
Add remote package filter sidebar
2 parents de3e900 + 02d4e13 commit 64cc839

15 files changed

Lines changed: 630 additions & 344 deletions

File tree

owlplug-client/src/main/java/com/owlplug/core/components/LazyViewRegistry.java

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,6 @@ public class LazyViewRegistry {
3737
public static final String NEW_ACCOUNT_VIEW = "NEW_ACCOUNT_VIEW";
3838
public static final String NEW_LINK_VIEW = "NEW_LINK_VIEW";
3939
public static final String WELCOME_VIEW = "WELCOME_VIEW";
40-
public static final String SOURCE_MENU_VIEW = "SOURCE_MENU_VIEW";
4140
public static final String NEW_SOURCE_VIEW = "NEW_SOURCE_VIEW";
4241
public static final String CRASH_RECOVERY_VIEW = "CRASH_RECOVERY_VIEW";
4342
public static final String LIST_DIRECTORY_VIEW = "LIST_DIRECTORY_VIEW";
@@ -63,7 +62,6 @@ public void preload() {
6362
preloadFxml(NEW_ACCOUNT_VIEW, "/fxml/dialogs/NewAccount.fxml");
6463
preloadFxml(NEW_LINK_VIEW, "/fxml/dialogs/NewLinkView.fxml");
6564
preloadFxml(WELCOME_VIEW, "/fxml/dialogs/WelcomeView.fxml");
66-
preloadFxml(SOURCE_MENU_VIEW, "/fxml/menu/SourceMenu.fxml");
6765
preloadFxml(NEW_SOURCE_VIEW, "/fxml/dialogs/NewSourceView.fxml");
6866
preloadFxml(CRASH_RECOVERY_VIEW, "/fxml/dialogs/CrashRecoveryView.fxml");
6967
preloadFxml(LIST_DIRECTORY_VIEW, "/fxml/dialogs/ListDirectoryView.fxml");

owlplug-client/src/main/java/com/owlplug/core/controllers/settings/InstallationSettingsController.java

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,23 +18,53 @@
1818

1919
package com.owlplug.core.controllers.settings;
2020

21+
import atlantafx.base.controls.ToggleSwitch;
22+
import atlantafx.base.theme.Styles;
23+
import com.owlplug.controls.Dialog;
24+
import com.owlplug.controls.DialogLayout;
2125
import com.owlplug.core.components.ApplicationDefaults.Prefs;
2226
import com.owlplug.core.controllers.BaseController;
27+
import com.owlplug.core.utils.FX;
28+
import com.owlplug.explore.controllers.NewSourceDialogController;
29+
import com.owlplug.explore.events.RemoteSourceUpdatedEvent;
30+
import com.owlplug.explore.model.RemoteSource;
31+
import com.owlplug.explore.services.ExploreService;
2332
import com.owlplug.plugin.ui.PluginFormatBadgeView;
2433
import java.io.File;
34+
import java.util.ArrayList;
35+
import java.util.List;
36+
import javafx.collections.FXCollections;
37+
import javafx.collections.ObservableList;
2538
import javafx.fxml.FXML;
39+
import javafx.geometry.Insets;
2640
import javafx.geometry.Pos;
41+
import javafx.scene.control.Button;
2742
import javafx.scene.control.CheckBox;
2843
import javafx.scene.control.Label;
44+
import javafx.scene.control.ListCell;
45+
import javafx.scene.control.ListView;
2946
import javafx.scene.control.TextField;
47+
import javafx.scene.control.Tooltip;
3048
import javafx.scene.layout.HBox;
3149
import javafx.scene.layout.Priority;
3250
import javafx.scene.layout.VBox;
51+
import org.kordamp.ikonli.javafx.FontIcon;
52+
import org.springframework.beans.factory.annotation.Autowired;
53+
import org.springframework.context.event.EventListener;
3354
import org.springframework.stereotype.Controller;
3455

3556
@Controller
3657
public class InstallationSettingsController extends BaseController {
3758

59+
@Autowired
60+
private ExploreService exploreService;
61+
@Autowired
62+
private NewSourceDialogController newSourceDialogController;
63+
64+
@FXML
65+
private ListView<RemoteSource> sourceListView;
66+
@FXML
67+
private Button addSourceButton;
3868
@FXML
3969
private CheckBox storeDirectoryCheckBox;
4070
@FXML
@@ -50,13 +80,38 @@ public class InstallationSettingsController extends BaseController {
5080
@FXML
5181
private VBox pathPreviewContainer;
5282

83+
private final ObservableList<RemoteSource> sourceItems = FXCollections.observableArrayList();
84+
5385
private Label vst2PathLabel;
5486
private Label vst3PathLabel;
5587
private Label auPathLabel;
5688
private Label lv2PathLabel;
5789

5890
@FXML
5991
public void initialize() {
92+
Label sourcePlaceholder = new Label("No remote sources configured.");
93+
sourcePlaceholder.getStyleClass().add("label-disabled");
94+
sourceListView.setPlaceholder(sourcePlaceholder);
95+
sourceListView.setItems(sourceItems);
96+
sourceListView.setCellFactory(lv -> new ListCell<>() {
97+
@Override
98+
protected void updateItem(RemoteSource remoteSource, boolean empty) {
99+
super.updateItem(remoteSource, empty);
100+
setText(null);
101+
setGraphic(empty || remoteSource == null ? null : buildSourceRow(remoteSource));
102+
}
103+
});
104+
105+
FontIcon addSourceIcon = new FontIcon("mdi2p-plus");
106+
addSourceIcon.setIconSize(14);
107+
addSourceButton.setGraphic(addSourceIcon);
108+
addSourceButton.setOnAction(e -> {
109+
newSourceDialogController.show();
110+
newSourceDialogController.startCreateSequence();
111+
});
112+
113+
refreshSources();
114+
60115
storeDirectoryRow.visibleProperty().bind(storeDirectoryCheckBox.selectedProperty());
61116
storeDirectoryRow.managedProperty().bind(storeDirectoryCheckBox.selectedProperty());
62117
warningSubDirectory.managedProperty().bind(warningSubDirectory.visibleProperty());
@@ -102,6 +157,74 @@ public void refresh() {
102157
storeDirectoryTextField.setText(
103158
getPreferences().get(Prefs.Explore.STORE_DIRECTORY, ""));
104159
refreshPathPreview();
160+
refreshSources();
161+
}
162+
163+
private void refreshSources() {
164+
List<RemoteSource> sources = new ArrayList<>();
165+
exploreService.getRemoteSources().forEach(sources::add);
166+
sourceItems.setAll(sources);
167+
}
168+
169+
private HBox buildSourceRow(RemoteSource remoteSource) {
170+
VBox infoBox = new VBox(2);
171+
Label nameLabel = new Label(remoteSource.getName());
172+
Label urlLabel = new Label(formatUrl(remoteSource.getDisplayUrl()));
173+
urlLabel.getStyleClass().add("label-disabled");
174+
infoBox.getChildren().addAll(nameLabel, urlLabel);
175+
HBox.setHgrow(infoBox, Priority.ALWAYS);
176+
177+
ToggleSwitch enabledToggle = new ToggleSwitch();
178+
enabledToggle.setSelected(remoteSource.isEnabled());
179+
Tooltip.install(enabledToggle, new Tooltip("Enable or disable this source"));
180+
enabledToggle.selectedProperty().addListener((obs, old, selected) ->
181+
exploreService.enableSource(remoteSource, selected));
182+
183+
Button deleteButton = new Button();
184+
deleteButton.getStyleClass().addAll(Styles.BUTTON_ICON, Styles.FLAT);
185+
deleteButton.setGraphic(new FontIcon("mdi2d-delete-outline"));
186+
Tooltip.install(deleteButton, new Tooltip("Remove source"));
187+
deleteButton.setOnAction(e -> confirmAndDeleteSource(remoteSource));
188+
189+
HBox row = new HBox(12, infoBox, enabledToggle, deleteButton);
190+
row.setAlignment(Pos.CENTER_LEFT);
191+
row.setPadding(new Insets(4));
192+
return row;
193+
}
194+
195+
private void confirmAndDeleteSource(RemoteSource remoteSource) {
196+
Dialog dialog = this.getDialogManager().newDialog();
197+
DialogLayout layout = new DialogLayout();
198+
layout.setHeading(new Label("Remove source"));
199+
layout.setBody(new Label("Do you really want to remove \"" + remoteSource.getName()
200+
+ "\" ? Packages from this source will no longer be available in Explore."));
201+
202+
Button cancelButton = new Button("Cancel");
203+
cancelButton.setOnAction(e -> dialog.close());
204+
205+
Button removeButton = new Button("Remove");
206+
removeButton.getStyleClass().add("button-danger");
207+
removeButton.setOnAction(e -> {
208+
dialog.close();
209+
exploreService.delete(remoteSource);
210+
refreshSources();
211+
});
212+
213+
layout.setActions(removeButton, cancelButton);
214+
dialog.setContent(layout);
215+
dialog.show();
216+
}
217+
218+
private String formatUrl(String url) {
219+
if (url == null) {
220+
return null;
221+
}
222+
return url.replace("http://", "").replace("https://", "");
223+
}
224+
225+
@EventListener
226+
private void handle(RemoteSourceUpdatedEvent event) {
227+
FX.run(this::refreshSources);
105228
}
106229

107230
private void refreshPathPreview() {
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
/* OwlPlug
2+
* Copyright (C) 2021 Arthur <dropsnorz@gmail.com>
3+
*
4+
* This file is part of OwlPlug.
5+
*
6+
* OwlPlug is free software: you can redistribute it and/or modify
7+
* it under the terms of the GNU General Public License version 3
8+
* as published by the Free Software Foundation.
9+
*
10+
* OwlPlug is distributed in the hope that it will be useful,
11+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
* GNU General Public License for more details.
14+
*
15+
* You should have received a copy of the GNU General Public License
16+
* along with OwlPlug. If not, see <https://www.gnu.org/licenses/>.
17+
*/
18+
19+
package com.owlplug.explore.components;
20+
21+
import com.owlplug.explore.model.search.ExploreFilterCriteria;
22+
import com.owlplug.explore.model.search.ExploreFilterCriteriaType;
23+
import com.owlplug.plugin.model.PluginFormat;
24+
import com.owlplug.plugin.model.PluginType;
25+
import java.util.ArrayList;
26+
import java.util.List;
27+
import javafx.beans.binding.Bindings;
28+
import javafx.beans.property.ObjectProperty;
29+
import javafx.beans.property.SimpleObjectProperty;
30+
import javafx.collections.FXCollections;
31+
import javafx.collections.ObservableSet;
32+
import org.springframework.stereotype.Component;
33+
34+
/**
35+
* Holds the current Explore view filter selections and derives the list of
36+
* {@link ExploreFilterCriteria} to send to {@code ExploreService#getRemotePackages}, since
37+
* package filtering is performed by a database query rather than an in-memory predicate.
38+
*/
39+
@Component
40+
public class ExploreFilterState {
41+
42+
private final ObservableSet<Long> selectedSourceIds = FXCollections.observableSet();
43+
private final ObservableSet<PluginFormat> selectedFormats = FXCollections.observableSet();
44+
private final ObservableSet<PluginType> selectedTypes = FXCollections.observableSet();
45+
private final ObservableSet<String> selectedPlatformTags = FXCollections.observableSet();
46+
47+
private final ObjectProperty<List<ExploreFilterCriteria>> criteriaList = new SimpleObjectProperty<>();
48+
49+
public ExploreFilterState() {
50+
criteriaList.bind(Bindings.createObjectBinding(this::buildCriteriaList,
51+
selectedSourceIds, selectedFormats, selectedTypes, selectedPlatformTags));
52+
}
53+
54+
private List<ExploreFilterCriteria> buildCriteriaList() {
55+
List<ExploreFilterCriteria> criteria = new ArrayList<>();
56+
if (!selectedSourceIds.isEmpty()) {
57+
criteria.add(new ExploreFilterCriteria(new ArrayList<>(selectedSourceIds),
58+
ExploreFilterCriteriaType.SOURCE_LIST));
59+
}
60+
if (!selectedFormats.isEmpty()) {
61+
List<String> formats = selectedFormats.stream()
62+
.map(format -> format.getName().toLowerCase())
63+
.toList();
64+
criteria.add(new ExploreFilterCriteria(formats, ExploreFilterCriteriaType.FORMAT_LIST));
65+
}
66+
if (!selectedTypes.isEmpty()) {
67+
criteria.add(new ExploreFilterCriteria(new ArrayList<>(selectedTypes),
68+
ExploreFilterCriteriaType.TYPE_LIST));
69+
}
70+
if (!selectedPlatformTags.isEmpty()) {
71+
criteria.add(new ExploreFilterCriteria(new ArrayList<>(selectedPlatformTags),
72+
ExploreFilterCriteriaType.PLATFORM_LIST));
73+
}
74+
return criteria;
75+
}
76+
77+
public ObservableSet<Long> getSelectedSourceIds() {
78+
return selectedSourceIds;
79+
}
80+
81+
public ObservableSet<PluginFormat> getSelectedFormats() {
82+
return selectedFormats;
83+
}
84+
85+
public ObservableSet<PluginType> getSelectedTypes() {
86+
return selectedTypes;
87+
}
88+
89+
public ObservableSet<String> getSelectedPlatformTags() {
90+
return selectedPlatformTags;
91+
}
92+
93+
public ObjectProperty<List<ExploreFilterCriteria>> criteriaListProperty() {
94+
return criteriaList;
95+
}
96+
97+
public void clearAll() {
98+
selectedSourceIds.clear();
99+
selectedFormats.clear();
100+
selectedTypes.clear();
101+
selectedPlatformTags.clear();
102+
}
103+
}

0 commit comments

Comments
 (0)