Skip to content

Commit

Permalink
Sort Options on Tours (defaults to Alphabetic) with User Settings sup…
Browse files Browse the repository at this point in the history
…port. Tours should be `Reload` from the related button after changing Settings (#90)

* fix: minor code fixes

Signed-off-by: Eleftherios Chrysochoidis <[email protected]>

* feat: Sort options and settings for tours

Signed-off-by: Eleftherios Chrysochoidis <[email protected]>

* fix: Sorting issues. Unit Test and Changelog

Signed-off-by: Eleftherios Chrysochoidis <[email protected]>

* rel: Bump version to 0.0.10

Signed-off-by: Eleftherios Chrysochoidis <[email protected]>

---------

Signed-off-by: Eleftherios Chrysochoidis <[email protected]>
  • Loading branch information
LefterisXris authored Nov 27, 2023
1 parent d052e2f commit c8aa6fe
Show file tree
Hide file tree
Showing 11 changed files with 196 additions and 29 deletions.
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,13 @@

## Unreleased

### Features

- Sort Options on Tours (defaults to Alphabetic) with User Settings support. Tours should be `Reload` from the related
button after changing Settings

## 0.0.9

### Support

- Support for all future Jetbrains' products 2023.* releases
Expand Down
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
pluginGroup=org.uom.lefterisxris.codetour
pluginName=CodeTour
# SemVer format -> https://semver.org
pluginVersion=0.0.9
pluginVersion=0.0.10

# See https://plugins.jetbrains.com/docs/intellij/build-number-ranges.html
# for insight into build numbers and IntelliJ Platform versions.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import lombok.Builder;
import lombok.Data;

import java.time.LocalDateTime;
import java.util.List;

@Data
Expand All @@ -12,18 +13,21 @@ public class Tour {
private String title; // The title of the Tour (visible on the tree)
private String description; // Description (visible on hover as tooltip)
private String nextTour;
private LocalDateTime createdAt;
private List<Step> steps;

public Tour() {
}

@Builder
public Tour(String id, String touFile, String title, String description, String nextTour, List<Step> steps) {
public Tour(String id, String touFile, String title, String description,
String nextTour, LocalDateTime createdAt, List<Step> steps) {
this.id = id;
this.tourFile = touFile;
this.title = title;
this.description = description;
this.nextTour = nextTour;
this.createdAt = createdAt;
this.steps = steps;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@
import org.jetbrains.annotations.Nls;
import org.jetbrains.annotations.Nullable;
import org.uom.lefterisxris.codetour.tours.ui.AppSettingsComponent;
import org.uom.lefterisxris.codetour.tours.ui.CodeTourNotifier;

import javax.swing.*;
import java.util.Optional;

/**
* Provides controller functionality for application settings.
Expand Down Expand Up @@ -41,19 +43,31 @@ public JComponent createComponent() {
@Override
public boolean isModified() {
AppSettingsState settings = AppSettingsState.getInstance();
return settingsComponent.isOnboardingAssistantOn() != settings.isOnboardingAssistantOn();
return settingsComponent.isOnboardingAssistantOn() != settings.isOnboardingAssistant()
|| (settingsComponent.getSortOption() != settings.getSortOption())
|| (settingsComponent.getSortDirection() != settings.getSortDirection());
}

@Override
public void apply() {
AppSettingsState settings = AppSettingsState.getInstance();
settings.setOnboardingAssistant(settingsComponent.isOnboardingAssistantOn());
settings.setSortDirection(settingsComponent.getSortDirection());
settings.setSortOption(Optional.ofNullable(settingsComponent.getSortOption())
.orElse(AppSettingsState.SortOptionE.TITLE));
}

@Override
public void reset() {
AppSettingsState settings = AppSettingsState.getInstance();
settingsComponent.setOnboardingAssistant(settings.isOnboardingAssistantOn());
settingsComponent.setOnboardingAssistant(settings.isOnboardingAssistant());
settingsComponent.setSortOption(settings.getSortOption());
settingsComponent.setSortDirection(settings.getSortDirection());
//TODO: This should be done automatically, instead of just prompting user

// Notify user to reload Settings
CodeTourNotifier.warn(null, "CodeTour User Settings Changed: " +
"Reload Tours from the bottom right button in Tool Pane Window");
}

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
package org.uom.lefterisxris.codetour.tours.service;

/**
*
*/

import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.components.PersistentStateComponent;
import com.intellij.openapi.components.State;
import com.intellij.openapi.components.Storage;
import com.intellij.util.xmlb.XmlSerializerUtil;
import lombok.Getter;
import lombok.Setter;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

Expand All @@ -24,9 +22,13 @@
name = "org.uom.lefterisxris.codetour.tours.service.AppSettingsState",
storages = @Storage("CodeTourSettings.xml")
)
@Getter
@Setter
public class AppSettingsState implements PersistentStateComponent<AppSettingsState> {

private boolean onboardingAssistant = true;
private SortOptionE sortOption = SortOptionE.TITLE;
private SortDirectionE sortDirection = SortDirectionE.ASC;

public static AppSettingsState getInstance() {
return ApplicationManager.getApplication().getService(AppSettingsState.class);
Expand All @@ -43,15 +45,17 @@ public void loadState(@NotNull AppSettingsState state) {
XmlSerializerUtil.copyBean(state, this);
}

public boolean isOnboardingAssistantOn() {
return onboardingAssistant;
public boolean isOnboardingAssistantOn() {return onboardingAssistant;}

public void toggleOnboardingAssistant() {
onboardingAssistant = !onboardingAssistant;
}

public void setOnboardingAssistant(boolean onboardingAssistant) {
this.onboardingAssistant = onboardingAssistant;
public enum SortOptionE {
TITLE, FILENAME, CREATION_DATE;
}

public void toggleOnboardingAssistant() {
onboardingAssistant = !onboardingAssistant;
public enum SortDirectionE {
ASC, DESC;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package org.uom.lefterisxris.codetour.tours.state;

import com.google.gson.TypeAdapter;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonToken;
import com.google.gson.stream.JsonWriter;
import com.intellij.openapi.diagnostic.Logger;

import java.io.IOException;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;

/**
* @author Eleftherios Chrysochoidis
* Date: 26/11/2023
*/
public class LocalDateTimeAdapter extends TypeAdapter<LocalDateTime> {

private static final Logger LOG = Logger.getInstance(LocalDateTimeAdapter.class);
private final DateTimeFormatter df = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");

@Override
public void write(JsonWriter jsonWriter, LocalDateTime localDateTime) throws IOException {
if (localDateTime == null)
jsonWriter.nullValue();
else
jsonWriter.value(localDateTime.format(df));
}

@Override
public LocalDateTime read(JsonReader jsonReader) throws IOException {
if (jsonReader.peek() == JsonToken.NULL) {
jsonReader.nextNull();
} else {
try {
return LocalDateTime.parse(jsonReader.nextString(), df);
} catch (Exception e) {
LOG.error("Could not parse Datetime value!", e);
}
}
return null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,9 @@ public class StateManager {
private static Optional<Integer> activeStepIndex = Optional.empty();
private static final Logger LOG = Logger.getInstance(StateManager.class);

private final Gson GSON = new GsonBuilder().setPrettyPrinting().create();
private final Gson GSON = new GsonBuilder().setPrettyPrinting()
.registerTypeAdapter(LocalDateTime.class, new LocalDateTimeAdapter())
.create();
private final ToursState state = new ToursState();
private final Project project;
private static LocalDateTime lastValidationTime = LocalDateTime.now().minusHours(2); // to trigger validation on init
Expand Down Expand Up @@ -140,15 +142,30 @@ public boolean shouldNotify(Project project) {
private List<Tour> loadTours(@NotNull Project project) {

final List<Tour> tours = new ArrayList<>();
var settings = AppSettingsState.getInstance();

// Add the Onboarding Tour if configured
if (AppSettingsState.getInstance().isOnboardingAssistantOn()) {
if (settings.isOnboardingAssistantOn()) {
final Tour onboardingTour = OnboardingAssistant.getInstance().getTour();
if (onboardingTour != null)
tours.add(onboardingTour);
}

tours.addAll(project.getBasePath() == null ? loadFromIndex(project) : loadFromFS());
var userTours = project.getBasePath() == null ? loadFromIndex(project) : loadFromFS();

// Sort User Tours. By default, they are sorted base on Title. Otherwise, it follows User Settings
Comparator<Tour> comparator = Comparator.comparing(Tour::getTitle);
switch (settings.getSortOption()) {
case FILENAME -> comparator = Comparator.comparing(Tour::getTourFile);
case CREATION_DATE ->
comparator = Comparator.comparing(Tour::getCreatedAt, Comparator.nullsLast(Comparator.naturalOrder()));
}
if (AppSettingsState.SortDirectionE.DESC.equals(settings.getSortDirection()))
comparator = comparator.reversed(); // ASC,DESC
LOG.info("Sorting Tours using: %s - %s".formatted(settings.getSortOption(), settings.getSortDirection()));
userTours.sort(comparator);

tours.addAll(userTours);
// Cache some info
tourFileNames.clear();
tourTitles.clear();
Expand Down Expand Up @@ -190,7 +207,7 @@ private List<Tour> loadFromIndex(@NotNull Project project) {
Tour tour;
try {
LOG.info("Reading (from Index) Tour from file: " + virtualFile.getName());
tour = new Gson().fromJson(new InputStreamReader(virtualFile.getInputStream()), Tour.class);
tour = GSON.fromJson(new InputStreamReader(virtualFile.getInputStream()), Tour.class);
} catch (IOException e) {
LOG.error("Skipping file: " + virtualFile.getName(), e);
return null;
Expand Down Expand Up @@ -224,10 +241,9 @@ private Optional<Tour> parse(VirtualFile file) {
try {
LOG.info("Reading (from FS) Tour from file: " + file.getName());
return Optional.of(
new Gson().fromJson(new InputStreamReader(file.getInputStream(), StandardCharsets.UTF_8), Tour.class));
} catch (IOException e) {
e.printStackTrace();
LOG.error("Skipping file: " + file.getName());
GSON.fromJson(new InputStreamReader(file.getInputStream(), StandardCharsets.UTF_8), Tour.class));
} catch (Exception e) {
LOG.error("Skipping file: " + file.getName(), e);
}
return Optional.empty();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
package org.uom.lefterisxris.codetour.tours.ui;

import com.intellij.openapi.ui.ComboBox;
import com.intellij.ui.components.JBCheckBox;
import com.intellij.ui.components.JBLabel;
import com.intellij.util.ui.FormBuilder;
import org.uom.lefterisxris.codetour.tours.service.AppSettingsState;

import javax.swing.*;

Expand All @@ -15,10 +18,18 @@ public class AppSettingsComponent {

private final JPanel mainPanel;
private final JBCheckBox onboardingAssistantCb = new JBCheckBox("Enable/disable virtual onboarding assistant");
private final ComboBox<AppSettingsState.SortOptionE> sortOption =
new ComboBox<>(AppSettingsState.SortOptionE.values());
private final ComboBox<AppSettingsState.SortDirectionE> sortDirection =
new ComboBox<>(AppSettingsState.SortDirectionE.values());

public AppSettingsComponent() {

mainPanel = FormBuilder.createFormBuilder()
.addComponent(onboardingAssistantCb, 1)
// .addComponent(new TitledSeparator())
.addLabeledComponent(new JBLabel("Tours sort option:"), sortOption, 2)
.addLabeledComponent(new JBLabel("Sort direction: ascending / descending"), sortDirection, 3)
.addComponentFillVertically(new JPanel(), 0)
.getPanel();
}
Expand All @@ -31,12 +42,22 @@ public JComponent getPreferredFocusedComponent() {
return onboardingAssistantCb;
}

public boolean isOnboardingAssistantOn() {
return onboardingAssistantCb.isSelected();
}
public boolean isOnboardingAssistantOn() {return onboardingAssistantCb.isSelected();}

public AppSettingsState.SortOptionE getSortOption() {return sortOption.getItem();}

public AppSettingsState.SortDirectionE getSortDirection() {return sortDirection.getItem();}

public void setOnboardingAssistant(boolean newStatus) {
onboardingAssistantCb.setSelected(newStatus);
}

public void setSortOption(AppSettingsState.SortOptionE newSortOption) {
sortOption.setItem(newSortOption);
}

public void setSortDirection(AppSettingsState.SortDirectionE newSortDirection) {
sortDirection.setItem(newSortDirection);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
import java.awt.*;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.time.LocalDateTime;
import java.util.List;
import java.util.*;
import java.util.stream.Collectors;
Expand Down Expand Up @@ -81,13 +82,13 @@ public JPanel getContent() {
* Handle plugin messaging
*/
public void registerMessageBusListener() {
project.getMessageBus().connect().subscribe(TourUpdateNotifier.TOPIC, (tour) -> {
project.getMessageBus().connect().subscribe(TourUpdateNotifier.TOPIC, (TourUpdateNotifier)(tour) -> {
stateManager.reloadState();
createToursTee(project);
selectTourLastStep(tour);
});

project.getMessageBus().connect().subscribe(StepSelectionNotifier.TOPIC, (step) -> {
project.getMessageBus().connect().subscribe(StepSelectionNotifier.TOPIC, (StepSelectionNotifier)(step) -> {
StateManager.getActiveTour().ifPresent(tour -> {
if (!toolWindow.isVisible())
toolWindow.show();
Expand Down Expand Up @@ -313,6 +314,7 @@ private void createNewTourListener() {
.touFile("newTour" + Props.TOUR_EXTENSION_FULL)
.title("A New Tour")
.description("A New Tour")
.createdAt(LocalDateTime.now())
.steps(new ArrayList<>())
.build();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
import java.awt.*;
import java.util.Optional;

import static org.uom.lefterisxris.codetour.tours.domain.OnboardingAssistant.ONBOARD_ASSISTANT_TITLE;

/**
* @author Eleftherios Chrysochoidis
* Date: 7/5/2022
Expand All @@ -32,10 +34,15 @@ public TourSelectionDialogWrapper(Project project, String title) {
JPanel dialogPanel = new JPanel(new BorderLayout());

final StateManager stateManager = new StateManager(project);
final int toursSize = stateManager.getTours().size();
var tours = stateManager.getTours();

// Onboarding Assistant should not be present in this selection
tours.removeIf(tour -> ONBOARD_ASSISTANT_TITLE.equals(tour.getTitle()));

final int toursSize = tours.size();
final Tour[] toursOptions = new Tour[toursSize];
for (int i = 0; i < toursSize; i++)
toursOptions[i] = stateManager.getTours().get(i);
toursOptions[i] = tours.get(i);

final ComboBox<Tour> comboBox = new ComboBox<>(toursOptions);
comboBox.addActionListener(e -> {
Expand Down
Loading

0 comments on commit c8aa6fe

Please sign in to comment.