Skip to content

Commit

Permalink
Add user guide to application (#199)
Browse files Browse the repository at this point in the history
* Add UserGuideTabPane

* Disable user guide tab when not selected

This prevents the user from clicking on the invisible tab which
holds the WebView.

* Improve pseudo-tab behaviour of user guide button

* Add URL whitelisting

* Update 'No External Site' page link

* Add logging whenever the state of the WebView updates

* Add method to refresh page

* Disable context menu of WebView

* Add auto-refresh 10s after page load failure

* Update 'No External Site' page link to be consistent

* Add top anchor to the user guide button

* Remove old help message
  • Loading branch information
ianyong authored Oct 27, 2020
1 parent 8a570be commit fd19491
Show file tree
Hide file tree
Showing 7 changed files with 165 additions and 18 deletions.
6 changes: 6 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,12 @@ dependencies {
implementation group: 'org.openjfx', name: 'javafx-graphics', version: javaFxVersion, classifier: 'win'
implementation group: 'org.openjfx', name: 'javafx-graphics', version: javaFxVersion, classifier: 'mac'
implementation group: 'org.openjfx', name: 'javafx-graphics', version: javaFxVersion, classifier: 'linux'
implementation group: 'org.openjfx', name: 'javafx-media', version: javaFxVersion, classifier: 'win'
implementation group: 'org.openjfx', name: 'javafx-media', version: javaFxVersion, classifier: 'mac'
implementation group: 'org.openjfx', name: 'javafx-media', version: javaFxVersion, classifier: 'linux'
implementation group: 'org.openjfx', name: 'javafx-web', version: javaFxVersion, classifier: 'win'
implementation group: 'org.openjfx', name: 'javafx-web', version: javaFxVersion, classifier: 'mac'
implementation group: 'org.openjfx', name: 'javafx-web', version: javaFxVersion, classifier: 'linux'

implementation group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.7.0'
implementation group: 'com.fasterxml.jackson.datatype', name: 'jackson-datatype-jsr310', version: '2.7.4'
Expand Down
38 changes: 26 additions & 12 deletions src/main/java/ay2021s1_cs2103_w16_3/finesse/ui/MainWindow.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,13 @@
import ay2021s1_cs2103_w16_3.finesse.ui.tabs.ExpenseTabPane;
import ay2021s1_cs2103_w16_3.finesse.ui.tabs.IncomeTabPane;
import ay2021s1_cs2103_w16_3.finesse.ui.tabs.OverviewTabPane;
import ay2021s1_cs2103_w16_3.finesse.ui.tabs.UserGuideTabPane;
import javafx.fxml.FXML;
import javafx.scene.control.Button;
import javafx.scene.control.SelectionModel;
import javafx.scene.control.Tab;
import javafx.scene.control.TabPane;
import javafx.scene.control.ToggleButton;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.StackPane;
import javafx.scene.text.Text;
Expand All @@ -35,9 +37,6 @@ public class MainWindow extends UiPart<Stage> {

private static final String WELCOME_MESSAGE = "Welcome to Fine$$e - your personal finance tracker."
+ "\nPlease enter the command \"help\" to view the user guide on the various commands you can use.";
private static final String USERGUIDE_URL = "https://ay2021s1-cs2103t-w16-3.github.io/tp/UserGuide.html";
private static final String HELP_MESSAGE = "Refer to the user guide: " + USERGUIDE_URL + "."
+ "\nPlease copy the url and paste it in your favourite browser to view all valid commands.";

private final Logger logger = LogsCenter.getLogger(getClass());

Expand All @@ -53,8 +52,6 @@ public class MainWindow extends UiPart<Stage> {
@FXML
private Button commandBoxButton;
@FXML
private Button helpButton;
@FXML
private StackPane resultDisplayPlaceholder;
@FXML
private StackPane statusbarPlaceholder;
Expand All @@ -67,6 +64,10 @@ public class MainWindow extends UiPart<Stage> {
@FXML
private Tab analyticsTab;
@FXML
private Tab userGuideTab;
@FXML
private ToggleButton userGuideButton;
@FXML
private TabPane tabPane;
@FXML
private Text expenseLimit;
Expand Down Expand Up @@ -118,6 +119,7 @@ public void fillInnerParts() {
* Initialize the contents of each tab.
*/
public void initializeTabs() {
// Set up tab contents.
OverviewTabPane overviewTabPane =
new OverviewTabPane(logic.getFilteredTransactionList(), logic.getMonthlyBudget());
overviewTab.setContent(overviewTabPane.getRoot());
Expand All @@ -133,15 +135,26 @@ public void initializeTabs() {
AnalyticsTabPane analyticsTabPane = new AnalyticsTabPane(logic.getMonthlyBudget());
analyticsTab.setContent(analyticsTabPane.getRoot());

UserGuideTabPane userGuideTabPane = new UserGuideTabPane();
userGuideTab.setContent(userGuideTabPane.getRoot());

// Set default selection.
SelectionModel<Tab> selectionModel = tabPane.getSelectionModel();
selectionModel.select(overviewTab);

// Set the bottom anchor after the tabs have been initialized.
AnchorPane.setBottomAnchor(tabPane, 0.0);

// Update UI state on tab change.
tabPane.getSelectionModel().selectedIndexProperty().addListener((observable, oldTabIndex, newTabIndex) ->
uiState.setCurrentTab(UiState.Tab.values()[newTabIndex.intValue()]));
tabPane.getSelectionModel().selectedIndexProperty().addListener((observable, oldTabIndex, newTabIndex) -> {
// Update UI state on tab change.
uiState.setCurrentTab(UiState.Tab.values()[newTabIndex.intValue()]);

// Disable the user guide tab when not selected to prevent the user from clicking the invisible tab.
userGuideTab.setDisable(newTabIndex.intValue() != UiState.Tab.USER_GUIDE.getTabIndex().getZeroBased());

// Set the user guide button to be unselected.
userGuideButton.setSelected(false);
});
}

/**
Expand Down Expand Up @@ -197,11 +210,12 @@ private void initializeWindowListeners() {
}

/**
* Displays the help message in the result display.
* Switches to the hidden user guide tab and sets the user guide button to be selected.
*/
@FXML
public void handleHelp() {
resultDisplay.setFeedbackToUser(HELP_MESSAGE);
public void switchToUserGuideTab() {
tabPane.getSelectionModel().select(userGuideTab);
userGuideButton.setSelected(true);
}

void show() {
Expand Down Expand Up @@ -231,7 +245,7 @@ private CommandResult executeCommand(String commandText) throws CommandException
resultDisplay.setFeedbackToUser(commandResult.getFeedbackToUser());

if (commandResult.isShowHelp()) {
handleHelp();
switchToUserGuideTab();
}

if (commandResult.isExit()) {
Expand Down
3 changes: 2 additions & 1 deletion src/main/java/ay2021s1_cs2103_w16_3/finesse/ui/UiState.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ public enum Tab {
OVERVIEW(1),
INCOME(2),
EXPENSES(3),
ANALYTICS(4);
ANALYTICS(4),
USER_GUIDE(5);

/** The index of the tab in the {@code TabPane}. */
private final Index tabIndex;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
package ay2021s1_cs2103_w16_3.finesse.ui.tabs;

import java.util.logging.Logger;

import ay2021s1_cs2103_w16_3.finesse.commons.core.LogsCenter;
import ay2021s1_cs2103_w16_3.finesse.ui.UiPart;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.application.Platform;
import javafx.fxml.FXML;
import javafx.scene.layout.StackPane;
import javafx.scene.web.WebEngine;
import javafx.scene.web.WebView;
import javafx.util.Duration;

/**
* Tab pane that displays the user guide.
*/
public class UserGuideTabPane extends UiPart<StackPane> {
private static final String FXML = "UserGuideTabPane.fxml";

// Links
private static final String GITHUB_PAGES_DOMAIN = "https://ay2021s1-cs2103t-w16-3.github.io";
private static final String USER_GUIDE_URL = "https://ay2021s1-cs2103t-w16-3.github.io/tp/UserGuide.html";
private static final String NO_EXTERNAL_SITE_PAGE_URL =
"https://ay2021s1-cs2103t-w16-3.github.io/tp/NoExternalSite.html";

// Constants
private static final double REFRESH_TIMEOUT_DELAY = 10000.0;

// Logging messages
private static final String WEB_ENGINE_EXTERNAL_SITE_REQUEST_BLOCKED =
"Request to load page on external site blocked";
private static final String WEB_ENGINE_WORKER_STATE_CANCELLED = "Page load cancelled: ";
private static final String WEB_ENGINE_WORKER_STATE_FAILED = "Unable to load page (no internet connection): ";
private static final String WEB_ENGINE_WORKER_STATE_RUNNING = "Loading page: ";
private static final String WEB_ENGINE_WORKER_STATE_SCHEDULED = "Page load scheduled: ";
private static final String WEB_ENGINE_WORKER_STATE_SUCCEEDED = "Page successfully loaded: ";

private final Logger logger = LogsCenter.getLogger(getClass());

@FXML
private WebView webView;

/**
* Constructs a {@code UserGuideTabPane}.
*/
public UserGuideTabPane() {
super(FXML);
initializeUserGuide();
}

/**
* Initializes the {@code WebEngine} that is backing the {@code WebView}.
*/
private void initializeUserGuide() {
// Disable right click.
webView.setContextMenuEnabled(false);

WebEngine webEngine = webView.getEngine();

webEngine.locationProperty().addListener((observableValue, oldUrl, newUrl) -> {
if (!newUrl.startsWith(GITHUB_PAGES_DOMAIN)) {
logger.info(WEB_ENGINE_EXTERNAL_SITE_REQUEST_BLOCKED);

// Block requests to external sites.
Platform.runLater(() -> {
// Load the 'No External Site' page.
webEngine.load(NO_EXTERNAL_SITE_PAGE_URL);
});
}
});

// Initialize refresh task.
Timeline refreshTask = new Timeline(new KeyFrame(Duration.millis(REFRESH_TIMEOUT_DELAY),
actionEvent -> refreshPage()));

webEngine.getLoadWorker().stateProperty().addListener((observableValue, oldState, newState) -> {
String location = webEngine.getLocation();

// Stop the refresh task to prevent multiple tasks running.
refreshTask.stop();

switch (newState) {
case CANCELLED:
logger.info(WEB_ENGINE_WORKER_STATE_CANCELLED + location);
break;
case FAILED:
logger.warning(WEB_ENGINE_WORKER_STATE_FAILED + location);
refreshTask.playFromStart();
break;
case RUNNING:
logger.info(WEB_ENGINE_WORKER_STATE_RUNNING + location);
break;
case SCHEDULED:
logger.info(WEB_ENGINE_WORKER_STATE_SCHEDULED + location);
break;
case SUCCEEDED:
logger.info(WEB_ENGINE_WORKER_STATE_SUCCEEDED + location);
break;
default:
}
});

webEngine.load(USER_GUIDE_URL);
}

/**
* Refreshes the WebView.
*/
public void refreshPage() {
// Cannot simply reload the page because if the page has not loaded a single time,
// reloading does not work.
String currentPage = webView.getEngine().getLocation();
webView.getEngine().load(currentPage);
}
}
5 changes: 2 additions & 3 deletions src/main/resources/view/DarkTheme.css
Original file line number Diff line number Diff line change
Expand Up @@ -268,16 +268,15 @@
-fx-text-fill: white;
}

#helpButton {
#userGuideButton {
-fx-background-color: transparent;
-fx-font-family: "Roboto Condensed";
-fx-font-size: 24px;
-fx-text-fill: #888888;
-fx-padding: 20, 0, 0, 0;
}

#helpButton:hover {
-fx-background-color: transparent;
#userGuideButton:hover, #userGuideButton:selected {
-fx-text-fill: white;
}

Expand Down
6 changes: 4 additions & 2 deletions src/main/resources/view/MainWindow.fxml
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@
<?import java.net.URL?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.Scene?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Tab?>
<?import javafx.scene.control.TabPane?>
<?import javafx.scene.control.ToggleButton?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.layout.BorderPane?>
<?import javafx.scene.layout.StackPane?>
Expand All @@ -31,8 +31,10 @@
<Tab fx:id="incomeTab" text="Incomes" closable="false" />
<Tab fx:id="expenseTab" text="Expenses" closable="false" />
<Tab fx:id="analyticsTab" text="Analytics" closable="false" />
<Tab fx:id="userGuideTab" text="" closable="false" />
</TabPane>
<Button fx:id="helpButton" text="Help" onMouseClicked="#handleHelp" AnchorPane.rightAnchor="0.0" />
<ToggleButton fx:id="userGuideButton" text="User Guide" onMouseClicked="#switchToUserGuideTab"
AnchorPane.topAnchor="0.0" AnchorPane.rightAnchor="0.0" />
</AnchorPane>
</center>

Expand Down
8 changes: 8 additions & 0 deletions src/main/resources/view/UserGuideTabPane.fxml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.layout.StackPane?>
<?import javafx.scene.web.WebView?>

<StackPane xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1">
<WebView fx:id="webView" />
</StackPane>

0 comments on commit fd19491

Please sign in to comment.