Skip to content

Commit

Permalink
Merge ffb565f into dc50061
Browse files Browse the repository at this point in the history
  • Loading branch information
tsoonjin committed Mar 27, 2016
2 parents dc50061 + ffb565f commit 3500061
Show file tree
Hide file tree
Showing 22 changed files with 781 additions and 5 deletions.
Binary file added docs/images/pickers/issue_picker_demo.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/images/pickers/issue_picker_highlight.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/images/pickers/issue_picker_main.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/images/pickers/issue_picker_toggle.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
26 changes: 26 additions & 0 deletions docs/issue_picker.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
## Issue Picker
A simple picker that allows you to select issues easily on current opened repository.

### Getting started
Press <kbd>Ctrl</kbd> + <kbd>I</kbd> to `launch` the issue picker.

![](images/pickers/issue_picker_main.png?raw=true)

The `Issue Picker` is divided into 3 main sections:
1. `Selected issues pane` : shows issues that will be selected upon confirmation
2. `Query field` : enter your query here to filter all issues in current repository
3. `Suggested issues pane`: lists all issues that matches your query.

### Selecting issues
Typing in the text field will highlight the issue that matches the query.

![](images/pickers/issue_picker_highlight.png?raw=true)

Clicking on a suggested issue will toggle its state and the text field will be disabled. An issue will be selected if
it is not chosen, or will be removed otherwise.

![](images/pickers/issue_picker_demo.gif?raw=true)

Finally, press `Confirm` or <kbd>Enter</kbd> to accept the selected issues.
Press `Cancel` or <kbd>Escape</kbd> to cancel and close the Issue Picker.

23 changes: 23 additions & 0 deletions src/main/java/backend/resource/TurboIssue.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
Expand Down Expand Up @@ -338,6 +339,28 @@ public static Optional<Integer> findIssueWithId(List<TurboIssue> issues, int id)
return Optional.empty();
}

/**
* Matching is done by matching all words separated by space in query
* @param issues
* @param query
* @return list of issues that contains the query
*/
public static List<TurboIssue> getMatchedIssues(List<TurboIssue> issues, String query){
List<String> queries = Arrays.asList(query.split("\\s"));
return issues.stream()
.filter(i -> Utility.containsIgnoreCase(i.getTitle() + " " + i.getId(), queries))
.collect(Collectors.toList());
}

/**
* @param issues
* @param query
* @return first issue that matches the given query
*/
public static Optional<TurboIssue> getFirstMatchingIssue(List<TurboIssue> issues, String query) {
return getMatchedIssues(issues, query).stream().findFirst();
}

@SuppressWarnings("unused")
private void ______BOILERPLATE______() {}

Expand Down
6 changes: 6 additions & 0 deletions src/main/java/ui/UI.java
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import ui.components.HTStatusBar;
import ui.components.KeyboardShortcuts;
import ui.components.StatusUI;
import ui.components.issuepicker.IssuePicker;
import ui.components.pickers.LabelPicker;
import ui.components.pickers.MilestonePicker;
import ui.issuepanel.PanelControl;
Expand All @@ -45,6 +46,7 @@
import java.util.concurrent.TimeUnit;

import static ui.components.KeyboardShortcuts.SWITCH_DEFAULT_REPO;
import static ui.components.KeyboardShortcuts.SHOW_ISSUE_PICKER;

public class UI extends Application implements EventDispatcher {

Expand Down Expand Up @@ -201,6 +203,7 @@ private void showMainWindow(String repoId) {
private void initialisePickers() {
new LabelPicker(this, mainStage);
new MilestonePicker(this, mainStage);
new IssuePicker(this, mainStage);
}

protected void registerTestEvents() {
Expand Down Expand Up @@ -357,6 +360,9 @@ private void setupGlobalKeyboardShortcuts(Scene scene) {
if (SWITCH_DEFAULT_REPO.match(event)) {
switchDefaultRepo();
}
if (SHOW_ISSUE_PICKER.match(event)) {
triggerEvent(new ShowIssuePickerEvent(logic.getModels().getIssues(), true));
}
});
}

Expand Down
6 changes: 4 additions & 2 deletions src/main/java/ui/components/KeyboardShortcuts.java
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,10 @@ public final class KeyboardShortcuts {
new KeyCodeCombination(KeyCode.D, KeyCombination.SHORTCUT_DOWN);
public static final KeyCodeCombination SWITCH_DEFAULT_REPO =
new KeyCodeCombination(KeyCode.R, KeyCombination.SHORTCUT_DOWN);
public static final KeyCodeCombination SHOW_ISSUES =
new KeyCodeCombination(KeyCode.I);
public static final KeyCodeCombination SHOW_ISSUE_PICKER =
new KeyCodeCombination(KeyCode.I, KeyCombination.SHORTCUT_DOWN);
public static final KeyCodeCombination SWITCH_BOARD =
new KeyCodeCombination(KeyCode.B, KeyCombination.SHORTCUT_DOWN);
public static final KeyCodeCombination UNDO_LABEL_CHANGES =
Expand All @@ -85,8 +89,6 @@ public final class KeyboardShortcuts {
new KeyCodeCombination(KeyCode.G);
public static final KeyCodeCombination SHOW_LABELS =
new KeyCodeCombination(KeyCode.L);
public static final KeyCodeCombination SHOW_ISSUES =
new KeyCodeCombination(KeyCode.I);
public static final KeyCodeCombination SHOW_MILESTONES =
new KeyCodeCombination(KeyCode.M);
public static final KeyCodeCombination SHOW_PULL_REQUESTS =
Expand Down
176 changes: 176 additions & 0 deletions src/main/java/ui/components/issuepicker/IssueCard.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
package ui.components.issuepicker;

import java.util.Optional;

import backend.resource.TurboIssue;
import backend.resource.TurboLabel;
import backend.resource.TurboMilestone;
import backend.resource.TurboUser;
import javafx.beans.binding.Bindings;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.control.Label;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.Background;
import javafx.scene.layout.BackgroundFill;
import javafx.scene.layout.CornerRadii;
import javafx.scene.layout.FlowPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.scene.text.Text;
import ui.GuiElement;

/**
* Represents an individual issue card not linked to any panel
*/
public class IssueCard extends VBox {

private static final String OCTICON_PULL_REQUEST = "\uf009";
private static final int CARD_WIDTH = 350;
private static final String OCTICON_COMMENT = "\uf02b";
private static final String OCTICON_ARROW_RIGHT = "\uf03e";
private static final Background FOCUSED_BACKGROUND = new Background(
new BackgroundFill(Color.CORNFLOWERBLUE, CornerRadii.EMPTY, Insets.EMPTY));
private static final Background DEFAULT_BACKGROUND = new Background(
new BackgroundFill(Color.WHITE, CornerRadii.EMPTY, Insets.EMPTY));

private final GuiElement guiElement;
private final FlowPane issueDetails = new FlowPane();
private final HBox authorAssigneeBox = new HBox();

public IssueCard(GuiElement guiElement, boolean isFocus){
this.guiElement = guiElement;
this.setFocused(isFocus);
setup();
}

private void setup() {
setupMainIssueCard();
TurboIssue issue = guiElement.getIssue();
Text issueTitle = new Text("#" + issue.getId() + " " + issue.getTitle());
issueTitle.setWrappingWidth(CARD_WIDTH);
issueTitle.getStyleClass().add("issue-panel-name");

if (issue.isCurrentlyRead()) {
issueTitle.getStyleClass().add("issue-panel-name-read");
}

if (!issue.isOpen()) {
issueTitle.getStyleClass().add("issue-panel-closed");
}

setupIssueDetailsBox();
setupAuthorAssigneeBox();
updateDetails();

setPadding(new Insets(3, 0, 3, 0));
setSpacing(1);

getChildren().addAll(issueTitle, issueDetails, authorAssigneeBox);
}

private void setupMainIssueCard() {
this.backgroundProperty().bind(Bindings.when(this.focusedProperty())
.then(FOCUSED_BACKGROUND).otherwise(DEFAULT_BACKGROUND));
this.setStyle("-fx-border-color:black; -fx-border-style:hidden hidden solid hidden; ");
}

private void setupIssueDetailsBox() {
issueDetails.setMaxWidth(CARD_WIDTH);
issueDetails.setPrefWrapLength(CARD_WIDTH);
issueDetails.setHgap(3);
issueDetails.setVgap(3);
}

private void setupAuthorAssigneeBox() {
authorAssigneeBox.setPrefWidth(CARD_WIDTH);
authorAssigneeBox.setPadding(new Insets(0, 0, 1, 0));
}

private void updateDetails() {
issueDetails.getChildren().clear();
TurboIssue issue = guiElement.getIssue();

if (issue.isPullRequest()) {
Label icon = new Label(OCTICON_PULL_REQUEST);
icon.getStyleClass().addAll("octicon", "issue-pull-request-icon");
issueDetails.getChildren().add(icon);
}

if (issue.getCommentCount() > 0){
Label commentIcon = new Label(OCTICON_COMMENT);
commentIcon.getStyleClass().addAll("octicon", "comments-label-button");
Label commentCount = new Label(Integer.toString(issue.getCommentCount()));

issueDetails.getChildren().add(commentIcon);
issueDetails.getChildren().add(commentCount);
}

for (TurboLabel label : guiElement.getLabels()) {
issueDetails.getChildren().add(label.getNode());
}

if (issue.getMilestone().isPresent() && guiElement.getMilestone().isPresent()) {
TurboMilestone milestone = guiElement.getMilestone().get();
issueDetails.getChildren().add(new Label(milestone.getTitle()));
}

if (issue.isPullRequest()) {
HBox authorBox = createDisplayUserBox(guiElement.getAuthor(), issue.getCreator());
authorAssigneeBox.getChildren().add(authorBox);
if (issue.getAssignee().isPresent()) {
Label rightArrow = new Label(OCTICON_ARROW_RIGHT);
rightArrow.getStyleClass().addAll("octicon", "pull-request-assign-icon");
authorAssigneeBox.getChildren().add(rightArrow);
}
}

if (issue.getAssignee().isPresent()) {
HBox assigneeBox = createDisplayUserBox(guiElement.getAssignee(), issue.getAssignee().get());
authorAssigneeBox.getChildren().add(assigneeBox);
}
}

/**
* Creates a box that displays a label of userName
* The avatar that belongs to the user will be prepended if TurboUser has it
* @param user
* @param userName
* @return
*/
private HBox createDisplayUserBox(Optional<TurboUser> user, String userName) {
HBox userBox = setupUserBox();
Label authorNameLabel = new Label(userName);
addAvatarIfPresent(userBox, user);
userBox.getChildren().addAll(authorNameLabel);
return userBox;
}

private void addAvatarIfPresent(HBox userBox, Optional<TurboUser> user) {
if (!user.isPresent()) return;
ImageView userAvatar = getAvatar(user.get());
userBox.getChildren().add(userAvatar);
}

private HBox setupUserBox() {
HBox userBox = new HBox();
userBox.setAlignment(Pos.BASELINE_CENTER);
return userBox;
}

/**
* Attempts to get the TurboUser's avatar
* @param user
* @return ImageView that contains the avatar image if it exists or an empty ImageView if it doesn't exist
*/
private ImageView getAvatar(TurboUser user) {
ImageView userAvatar = new ImageView();
Image userAvatarImage = user.getAvatarImage();
if (userAvatarImage != null) {
userAvatar.setImage(userAvatarImage);
}
return userAvatar;
}
}
26 changes: 26 additions & 0 deletions src/main/java/ui/components/issuepicker/IssuePicker.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package ui.components.issuepicker;

import java.util.Optional;

import backend.resource.MultiModel;
import javafx.application.Platform;
import javafx.stage.Stage;
import ui.UI;
import util.events.ShowIssuePickerEventHandler;

public class IssuePicker {

private final Stage stage;

public IssuePicker(UI ui, Stage stage) {
this.stage = stage;
ui.registerEvent((ShowIssuePickerEventHandler) e ->
Platform.runLater(() -> showIssuePicker(ui.logic.getModels())));
}

private Optional<String> showIssuePicker(MultiModel models) {
IssuePickerDialog issuePickerDialog = new IssuePickerDialog(stage, models);
return issuePickerDialog.showAndWait();

}
}
Loading

0 comments on commit 3500061

Please sign in to comment.