diff --git a/README.md b/README.md index 0d7c78d..5b33c1f 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,11 @@ # dev-tools -Common development tools in one app +Common development tools in one app. +* [Json Editor](#json_editor) +* [UUID/Password Generator](#generator) +* [Hash Calculator](#hash_calculator) +* [Epoch Converter](#epoch_converter) +* [Regular Expression Tester](#regex) +* [Rest API Tester](#rest_api) ## Installation This is a Maven JavaFX application. @@ -13,6 +19,8 @@ or download the latest release. ## Tools List + + ### Json Editor * Json Pretty Print with Highlighting * Json Validation @@ -20,12 +28,16 @@ or download the latest release. ![](./images/json_editor.png) -### Generator + + +### UUID/Password Generator * UUID Generator * Password Generator ![](./images/generator.png) + + ### Hash Calculator * Hash Functions * URL Encode/Decode @@ -33,6 +45,8 @@ or download the latest release. ![](./images/hash_calculator.png) + + ### Epoch Converter * Current Unix Epoch Time * Timestamp to Human Date @@ -40,6 +54,21 @@ or download the latest release. ![](./images/epoch_converter.png) + + +### Regular Expression Tester +* Regex Flags +* Capturing Groups + +![](./images/regex.png) + + + +### Rest API Tester +* Rest API Testing Client + +![](./images/rest_api.png) + ## Contributing If you find this project useful and want to contribute, please open an issue or create a PR. diff --git a/images/epoch_converter.png b/images/epoch_converter.png index 36c8927..417c0f5 100644 Binary files a/images/epoch_converter.png and b/images/epoch_converter.png differ diff --git a/images/generator.png b/images/generator.png index f2c0c98..f7f0efe 100644 Binary files a/images/generator.png and b/images/generator.png differ diff --git a/images/hash_calculator.png b/images/hash_calculator.png index c2ab7bc..eca5c99 100644 Binary files a/images/hash_calculator.png and b/images/hash_calculator.png differ diff --git a/images/json_editor.png b/images/json_editor.png index f5983e2..261b32c 100644 Binary files a/images/json_editor.png and b/images/json_editor.png differ diff --git a/images/regex.png b/images/regex.png new file mode 100644 index 0000000..363108e Binary files /dev/null and b/images/regex.png differ diff --git a/images/rest_api.png b/images/rest_api.png new file mode 100644 index 0000000..0a3b7f2 Binary files /dev/null and b/images/rest_api.png differ diff --git a/src/main/java/com/github/reugn/devtools/Main.java b/src/main/java/com/github/reugn/devtools/Main.java index 8c3813a..dd77e06 100644 --- a/src/main/java/com/github/reugn/devtools/Main.java +++ b/src/main/java/com/github/reugn/devtools/Main.java @@ -20,7 +20,7 @@ public void start(Stage primaryStage) throws Exception { Parent root = FXMLLoader.load(getClass().getResource("/views/main.fxml")); primaryStage.setTitle("Dev-tools"); primaryStage.getIcons().add(new Image("/images/icons8-toolbox-64.png")); - Scene scene = new Scene(root, 800, 500); + Scene scene = new Scene(root, 900, 500); scene.getStylesheets().addAll("/css/main.css", "/css/json-highlighting.css"); primaryStage.setScene(scene); primaryStage.show(); diff --git a/src/main/java/com/github/reugn/devtools/controllers/EpochController.java b/src/main/java/com/github/reugn/devtools/controllers/EpochController.java index a6da954..0f6d377 100644 --- a/src/main/java/com/github/reugn/devtools/controllers/EpochController.java +++ b/src/main/java/com/github/reugn/devtools/controllers/EpochController.java @@ -1,46 +1,56 @@ package com.github.reugn.devtools.controllers; import com.github.reugn.devtools.services.EpochService; +import com.github.reugn.devtools.utils.Elements; import com.github.reugn.devtools.utils.Logger; import javafx.event.ActionEvent; import javafx.fxml.FXML; import javafx.fxml.Initializable; import javafx.geometry.Insets; -import javafx.scene.control.Button; -import javafx.scene.control.Label; -import javafx.scene.control.TextArea; -import javafx.scene.control.TextField; -import javafx.scene.layout.*; -import javafx.scene.paint.Color; +import javafx.scene.control.*; +import javafx.scene.input.KeyEvent; +import javafx.scene.layout.Border; +import javafx.scene.layout.GridPane; +import javafx.scene.layout.HBox; import java.net.URL; import java.time.LocalDateTime; import java.util.ResourceBundle; +import java.util.TimeZone; public class EpochController implements Initializable, Logger { - public Label currentEpochLabel; - - public TextField currentEpoch; - - public Button currentEpochRefreshButton; - - public TextField tsToHumanField; - - public Button tsToHumanButton; - - public TextArea tsToHumanResult; - - public Button humanToTsButton; - - public TextArea humanToTsResult; - - public TextField epochYear; - public TextField epochMonth; - public TextField epochDay; - public TextField epochHour; - public TextField epochMinute; - public TextField epochSecond; + @FXML + private Label currentEpochLabel; + @FXML + private TextField currentEpoch; + @FXML + private Button currentEpochRefreshButton; + @FXML + private TextField tsToHumanField; + @FXML + private Button tsToHumanButton; + @FXML + private TextArea tsToHumanResult; + @FXML + private Button humanToTsButton; + @FXML + private TextArea humanToTsResult; + @FXML + private TextField epochYear; + @FXML + private TextField epochMonth; + @FXML + private TextField epochDay; + @FXML + private TextField epochHour; + @FXML + private TextField epochMinute; + @FXML + private TextField epochSecond; + @FXML + private ComboBox timeZoneComboBox; + private int timeZoneComboBoxIndex; @FXML private void handleRefreshEpoch(final ActionEvent event) { @@ -55,8 +65,7 @@ private void handleTsToHumanEpoch(final ActionEvent event) { String result = EpochService.toHumanEpoch(dt); tsToHumanResult.setText(result); } catch (Exception e) { - tsToHumanField.setBorder(new Border(new BorderStroke(Color.RED, - BorderStrokeStyle.SOLID, new CornerRadii(3), BorderWidths.DEFAULT))); + tsToHumanField.setBorder(Elements.alertBorder); tsToHumanResult.setText(""); } } @@ -71,13 +80,30 @@ private void handleHumanToTsEpoch(final ActionEvent event) { int hour = EpochService.validate(epochHour, 0, 24); int minute = EpochService.validate(epochMinute, 0, 59); int second = EpochService.validate(epochSecond, 0, 59); - String result = EpochService.toTsEpoch(year, month, day, hour, minute, second); + String timeZone = timeZoneComboBox.getSelectionModel().getSelectedItem(); + String result = EpochService.toTsEpoch(year, month, day, hour, minute, second, timeZone); humanToTsResult.setText(result); } catch (Exception e) { humanToTsResult.setText(""); } } + @FXML + private void handleTimeZoneSearch(KeyEvent keyEvent) { + String key = keyEvent.getText(); + if (key.length() == 0) return; + int i = 0; + for (String item : timeZoneComboBox.getItems()) { + if (item.toLowerCase().startsWith(key) && i > timeZoneComboBoxIndex) { + timeZoneComboBox.setValue(item); + timeZoneComboBoxIndex = i; + return; + } + i++; + } + timeZoneComboBoxIndex = 0; + } + private void resetBorders() { epochYear.setBorder(Border.EMPTY); epochMonth.setBorder(Border.EMPTY); @@ -89,11 +115,11 @@ private void resetBorders() { @Override public void initialize(URL location, ResourceBundle resources) { - HBox.setMargin(currentEpochLabel, new Insets(20, 5, 15, 0)); - HBox.setMargin(currentEpoch, new Insets(15, 5, 15, 0)); - HBox.setMargin(currentEpochRefreshButton, new Insets(15, 5, 15, 0)); - HBox.setMargin(tsToHumanField, new Insets(15, 5, 15, 0)); - HBox.setMargin(tsToHumanButton, new Insets(15, 5, 15, 0)); + HBox.setMargin(currentEpochLabel, new Insets(15, 5, 10, 0)); + HBox.setMargin(currentEpoch, new Insets(10, 5, 10, 0)); + HBox.setMargin(currentEpochRefreshButton, new Insets(10, 5, 10, 0)); + HBox.setMargin(tsToHumanField, new Insets(10, 5, 10, 0)); + HBox.setMargin(tsToHumanButton, new Insets(10, 5, 10, 0)); GridPane.setMargin(epochYear, new Insets(10, 5, 0, 0)); GridPane.setMargin(epochMonth, new Insets(10, 5, 0, 0)); @@ -102,6 +128,7 @@ public void initialize(URL location, ResourceBundle resources) { GridPane.setMargin(epochMinute, new Insets(10, 5, 0, 0)); GridPane.setMargin(epochSecond, new Insets(10, 5, 0, 0)); GridPane.setMargin(humanToTsButton, new Insets(10, 5, 0, 0)); + GridPane.setMargin(timeZoneComboBox, new Insets(10, 5, 0, 0)); long now = System.currentTimeMillis(); currentEpoch.setText(Long.toString(now)); @@ -114,5 +141,9 @@ public void initialize(URL location, ResourceBundle resources) { epochHour.setText(String.valueOf(date.getHour())); epochMinute.setText(String.valueOf(date.getMinute())); epochSecond.setText(String.valueOf(date.getSecond())); + + timeZoneComboBox.getItems().setAll(TimeZone.getAvailableIDs()); + timeZoneComboBox.setValue("UTC"); + timeZoneComboBoxIndex = 0; } } diff --git a/src/main/java/com/github/reugn/devtools/controllers/GeneratorController.java b/src/main/java/com/github/reugn/devtools/controllers/GeneratorController.java index 4fecd6b..dfc6102 100644 --- a/src/main/java/com/github/reugn/devtools/controllers/GeneratorController.java +++ b/src/main/java/com/github/reugn/devtools/controllers/GeneratorController.java @@ -1,5 +1,6 @@ package com.github.reugn.devtools.controllers; +import com.github.reugn.devtools.utils.Elements; import com.github.reugn.devtools.utils.Logger; import com.github.reugn.devtools.utils.PasswordGenerator; import javafx.event.ActionEvent; @@ -7,8 +8,8 @@ import javafx.fxml.Initializable; import javafx.geometry.Insets; import javafx.scene.control.*; -import javafx.scene.layout.*; -import javafx.scene.paint.Color; +import javafx.scene.layout.Border; +import javafx.scene.layout.VBox; import java.net.URL; import java.security.InvalidParameterException; @@ -19,37 +20,26 @@ public class GeneratorController implements Initializable, Logger { @FXML private ComboBox uuidAmount; - @FXML private Label uuidAmountLabel; - @FXML private CheckBox uuidUpperCase; - @FXML private CheckBox uuidHyphens; - @FXML private TextArea generatorResult; - @FXML private CheckBox pwdLowChars; - @FXML private CheckBox pwdDigits; - @FXML private CheckBox pwdUpperChars; - @FXML private CheckBox pwdSymbols; - @FXML private TextField pwdLength; - @FXML private Label pwdLengthLabel; - @FXML private Button clearButton; @@ -77,8 +67,7 @@ private void handleGeneratePasswordAction(final ActionEvent actionEvent) { pwdLength.setBorder(Border.EMPTY); length = validatePasswordLength(); } catch (Exception e) { - pwdLength.setBorder(new Border(new BorderStroke(Color.RED, - BorderStrokeStyle.SOLID, new CornerRadii(3), BorderWidths.DEFAULT))); + pwdLength.setBorder(Elements.alertBorder); return; } PasswordGenerator generator = new PasswordGenerator.PasswordGeneratorBuilder() diff --git a/src/main/java/com/github/reugn/devtools/controllers/HashController.java b/src/main/java/com/github/reugn/devtools/controllers/HashController.java index d98228b..47802b7 100644 --- a/src/main/java/com/github/reugn/devtools/controllers/HashController.java +++ b/src/main/java/com/github/reugn/devtools/controllers/HashController.java @@ -48,6 +48,7 @@ private void handleClear(final ActionEvent event) { @FXML private void handleCalculate(final ActionEvent event) { + hashMessage.setText(""); try { String enc; String data = hashInput.getText(); diff --git a/src/main/java/com/github/reugn/devtools/controllers/JsonController.java b/src/main/java/com/github/reugn/devtools/controllers/JsonController.java index f7046bc..9b9d9d6 100644 --- a/src/main/java/com/github/reugn/devtools/controllers/JsonController.java +++ b/src/main/java/com/github/reugn/devtools/controllers/JsonController.java @@ -1,180 +1,26 @@ package com.github.reugn.devtools.controllers; -import com.github.reugn.devtools.services.JsonService; -import com.github.reugn.devtools.utils.JsonSearchState; -import com.github.reugn.devtools.utils.Logger; -import javafx.event.ActionEvent; import javafx.fxml.FXML; -import javafx.fxml.Initializable; -import javafx.geometry.Insets; -import javafx.scene.control.Button; -import javafx.scene.control.Label; -import javafx.scene.control.ToolBar; -import javafx.scene.input.KeyCode; -import javafx.scene.input.KeyEvent; -import javafx.scene.layout.HBox; -import javafx.scene.paint.Color; -import org.controlsfx.control.textfield.CustomTextField; -import org.fxmisc.richtext.CodeArea; -import org.fxmisc.richtext.model.StyleSpans; -import org.fxmisc.richtext.model.StyleSpansBuilder; import java.net.URL; -import java.util.Collection; -import java.util.Collections; -import java.util.Optional; import java.util.ResourceBundle; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -public class JsonController implements Initializable, Logger { - - public Button clearSpacesButton; - public Button formatButton; - public Button clearButton; - public Button buttonCloseSearch; - public ToolBar barSearch; - public CustomTextField fieldSearch; - public Button buttonSearchUp; - public Button buttonSearchDown; - public Label labelMatches; - - private JsonSearchState searchState; - - @FXML - private Label jsonMessage; - - @FXML - private CodeArea jsonArea; - - private static final Pattern JSON_REGEX = Pattern.compile("(?\\{|\\})|" + - "(?\\\".*\\\")\\s*:\\s*|" + - "(?\\\".*\\\")|" + - "\\[(?.*)\\]|" + - "(?\\d+.?\\d*)|" + - "(?true|false)|" + - "(?null)"); - - @FXML - private void handlePrettyPrint(final ActionEvent event) { - String data = jsonArea.getText(); - try { - String pretty = JsonService.format(data); - jsonArea.replaceText(pretty); - jsonMessage.setText(""); - } catch (Exception e) { - jsonMessage.setText("Invalid JSON"); - } - } - - private StyleSpans> computeHighlighting(String text) { - Matcher matcher = JSON_REGEX.matcher(text); - int lastKwEnd = 0; - StyleSpansBuilder> spansBuilder = new StyleSpansBuilder<>(); - while (matcher.find()) { - String styleClass - = matcher.group("JSONPROPERTY") != null ? "json_property" - : matcher.group("JSONARRAY") != null ? "json_array" - : matcher.group("JSONCURLY") != null ? "json_curly" - : matcher.group("JSONBOOL") != null ? "json_bool" - : matcher.group("JSONNULL") != null ? "json_null" - : matcher.group("JSONNUMBER") != null ? "json_number" - : matcher.group("JSONVALUE") != null ? "json_value" - : null; - spansBuilder.add(Collections.emptyList(), matcher.start() - lastKwEnd); - spansBuilder.add(Collections.singleton(styleClass), matcher.end() - matcher.start()); - lastKwEnd = matcher.end(); - } - spansBuilder.add(Collections.emptyList(), text.length() - lastKwEnd); - return spansBuilder.create(); - } - - @FXML - private void handleClearSpaces(final ActionEvent event) { - String data = jsonArea.getText(); - try { - String cleared = JsonService.clearSpaces(data); - jsonArea.replaceText(cleared); - jsonMessage.setText(""); - } catch (Exception e) { - jsonMessage.setText("Invalid JSON"); - } - } - - @FXML - private void handleClear(final ActionEvent event) { - jsonMessage.setText(""); - jsonArea.replaceText(""); - handleCloseSearchAction(event); - } - - @FXML - private void handleSearchBarEvent(final KeyEvent event) { - if (event.isControlDown() && event.getCode() == KeyCode.F) { - barSearch.setVisible(true); - barSearch.setManaged(true); - fieldSearch.requestFocus(); - } else if (event.getCode() == KeyCode.ESCAPE) { - barSearch.setVisible(false); - barSearch.setManaged(false); - } - } - - @FXML - private void handleCloseSearchAction(final ActionEvent event) { - jsonArea.deselect(); - fieldSearch.setText(""); - labelMatches.setText(""); - barSearch.setVisible(false); - barSearch.setManaged(false); - } +public class JsonController extends TabPaneController { @FXML - private void handleSearchBarAction(final KeyEvent event) { - if (event.getCode() == KeyCode.ENTER) { - handleSearchDownAction(new ActionEvent()); - } - } - - @FXML - private void handleSearchUpAction(final ActionEvent event) { - Optional span = getSearchState().prev(); - jsonArea.deselect(); - span.ifPresent(searchSpan -> jsonArea.selectRange(searchSpan.getFrom(), searchSpan.getTo())); - labelMatches.setText(getSearchState().toString()); - } + private static JsonController self; - @FXML - private void handleSearchDownAction(final ActionEvent event) { - Optional span = getSearchState().next(); - jsonArea.deselect(); - span.ifPresent(searchSpan -> jsonArea.selectRange(searchSpan.getFrom(), searchSpan.getTo())); - labelMatches.setText(getSearchState().toString()); + @Override + public void initialize(URL url, ResourceBundle resourceBundle) { + self = this; } - private JsonSearchState getSearchState() { - if (searchState == null || !searchState.isValid(fieldSearch.getText(), jsonArea.getText())) { - searchState = new JsonSearchState(fieldSearch.getText(), jsonArea.getText()); - } - return searchState; + static JsonController instance() { + return self; } @Override - public void initialize(URL location, ResourceBundle resources) { - jsonArea.setPrefHeight(1024); - jsonArea.setWrapText(true); - jsonArea.textProperty().addListener((obs, oldText, newText) -> { - jsonArea.setStyleSpans(0, computeHighlighting(newText)); - }); - jsonMessage.setPadding(new Insets(5)); - jsonMessage.setTextFill(Color.RED); - - HBox.setMargin(clearSpacesButton, new Insets(0, 5, 10, 0)); - HBox.setMargin(formatButton, new Insets(0, 5, 10, 0)); - HBox.setMargin(clearButton, new Insets(0, 5, 10, 0)); - HBox.setMargin(jsonMessage, new Insets(0, 5, 10, 0)); - - barSearch.setVisible(false); - barSearch.setManaged(false); + protected String getInnerResource() { + return "/views/json_tab.fxml"; } } diff --git a/src/main/java/com/github/reugn/devtools/controllers/JsonTabController.java b/src/main/java/com/github/reugn/devtools/controllers/JsonTabController.java new file mode 100644 index 0000000..2635dad --- /dev/null +++ b/src/main/java/com/github/reugn/devtools/controllers/JsonTabController.java @@ -0,0 +1,201 @@ +package com.github.reugn.devtools.controllers; + +import com.github.reugn.devtools.services.JsonService; +import com.github.reugn.devtools.utils.JsonSearchState; +import com.github.reugn.devtools.utils.Logger; +import javafx.event.ActionEvent; +import javafx.fxml.FXML; +import javafx.fxml.Initializable; +import javafx.geometry.Insets; +import javafx.scene.control.Button; +import javafx.scene.control.Label; +import javafx.scene.control.ToolBar; +import javafx.scene.input.KeyCode; +import javafx.scene.input.KeyEvent; +import javafx.scene.layout.HBox; +import javafx.scene.paint.Color; +import org.controlsfx.control.textfield.CustomTextField; +import org.fxmisc.richtext.CodeArea; +import org.fxmisc.richtext.model.StyleSpans; +import org.fxmisc.richtext.model.StyleSpansBuilder; + +import java.net.URL; +import java.util.Collection; +import java.util.Collections; +import java.util.Optional; +import java.util.ResourceBundle; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class JsonTabController implements Initializable, Logger { + + @FXML + private Button clearSpacesButton; + @FXML + private Button formatButton; + @FXML + private Button clearButton; + @FXML + private Button buttonCloseSearch; + @FXML + private ToolBar barSearch; + @FXML + private CustomTextField fieldSearch; + @FXML + private Button buttonSearchUp; + @FXML + private Button buttonSearchDown; + @FXML + private Label labelMatches; + @FXML + private Label jsonMessage; + @FXML + private CodeArea jsonArea; + + private JsonSearchState searchState; + + private static final Pattern JSON_REGEX = Pattern.compile("(?\\{|\\})|" + + "(?\\\".*\\\")\\s*:\\s*|" + + "(?\\\".*\\\")|" + + "\\[(?.*)\\]|" + + "(?\\d+.?\\d*)|" + + "(?true|false)|" + + "(?null)"); + + @FXML + private void handlePrettyPrint(final ActionEvent event) { + String data = jsonArea.getText(); + try { + String pretty = JsonService.format(data); + JsonController.instance().innerTabPane.getSelectionModel().getSelectedItem() + .setText(tabTitle(JsonService.clearSpaces(data))); + jsonArea.replaceText(pretty); + jsonMessage.setText(""); + } catch (Exception e) { + jsonMessage.setText("Invalid JSON"); + } + } + + @FXML + private void handleClearSpaces(final ActionEvent event) { + String data = jsonArea.getText(); + try { + String cleared = JsonService.clearSpaces(data); + JsonController.instance().innerTabPane.getSelectionModel().getSelectedItem().setText(tabTitle(cleared)); + jsonArea.replaceText(cleared); + jsonMessage.setText(""); + } catch (Exception e) { + jsonMessage.setText("Invalid JSON"); + } + } + + private static int tabTitleLength = 12; + + private String tabTitle(String json) { + return json.length() > tabTitleLength ? json.substring(0, tabTitleLength) : json; + } + + private StyleSpans> computeHighlighting(String text) { + Matcher matcher = JSON_REGEX.matcher(text); + int lastKwEnd = 0; + StyleSpansBuilder> spansBuilder = new StyleSpansBuilder<>(); + while (matcher.find()) { + String styleClass + = matcher.group("JSONPROPERTY") != null ? "json_property" + : matcher.group("JSONARRAY") != null ? "json_array" + : matcher.group("JSONCURLY") != null ? "json_curly" + : matcher.group("JSONBOOL") != null ? "json_bool" + : matcher.group("JSONNULL") != null ? "json_null" + : matcher.group("JSONNUMBER") != null ? "json_number" + : matcher.group("JSONVALUE") != null ? "json_value" + : null; + spansBuilder.add(Collections.emptyList(), matcher.start() - lastKwEnd); + spansBuilder.add(Collections.singleton(styleClass), matcher.end() - matcher.start()); + lastKwEnd = matcher.end(); + } + spansBuilder.add(Collections.emptyList(), text.length() - lastKwEnd); + return spansBuilder.create(); + } + + @FXML + private void handleClear(final ActionEvent event) { + jsonMessage.setText(""); + jsonArea.replaceText(""); + JsonController.instance().innerTabPane.getSelectionModel().getSelectedItem().setText("New"); + handleCloseSearchAction(event); + } + + @FXML + private void handleSearchBarEvent(final KeyEvent event) { + if (event.isControlDown() && event.getCode() == KeyCode.F) { + barSearch.setVisible(true); + barSearch.setManaged(true); + fieldSearch.requestFocus(); + } else if (event.getCode() == KeyCode.ESCAPE) { + barSearch.setVisible(false); + barSearch.setManaged(false); + } + } + + @FXML + private void handleCloseSearchAction(final ActionEvent event) { + jsonArea.deselect(); + fieldSearch.setText(""); + labelMatches.setText(""); + barSearch.setVisible(false); + barSearch.setManaged(false); + } + + @FXML + private void handleSearchBarAction(final KeyEvent event) { + if (event.getCode() == KeyCode.ENTER) { + handleSearchDownAction(new ActionEvent()); + } + } + + @FXML + private void handleSearchUpAction(final ActionEvent event) { + doSearch(getSearchState().prev()); + } + + @FXML + private void handleSearchDownAction(final ActionEvent event) { + doSearch(getSearchState().next()); + } + + private void doSearch(Optional span) { + jsonArea.deselect(); + span.ifPresent(searchSpan -> { + jsonArea.moveTo(searchSpan.getFrom()); + jsonArea.requestFollowCaret(); + jsonArea.selectRange(searchSpan.getFrom(), searchSpan.getTo()); + }); + labelMatches.setText(getSearchState().toString()); + } + + private JsonSearchState getSearchState() { + if (searchState == null || !searchState.isValid(fieldSearch.getText(), jsonArea.getText())) { + searchState = new JsonSearchState(fieldSearch.getText(), jsonArea.getText()); + } + return searchState; + } + + @Override + public void initialize(URL location, ResourceBundle resources) { + jsonArea.setPrefHeight(1024); + jsonArea.setWrapText(true); + jsonArea.textProperty().addListener((obs, oldText, newText) -> { + jsonArea.setStyleSpans(0, computeHighlighting(newText)); + }); + jsonMessage.setPadding(new Insets(5)); + jsonMessage.setTextFill(Color.RED); + + HBox.setMargin(clearSpacesButton, new Insets(0, 5, 10, 0)); + HBox.setMargin(formatButton, new Insets(0, 5, 10, 0)); + HBox.setMargin(clearButton, new Insets(0, 5, 10, 0)); + HBox.setMargin(jsonMessage, new Insets(0, 5, 10, 0)); + + barSearch.setVisible(false); + barSearch.setManaged(false); + } +} diff --git a/src/main/java/com/github/reugn/devtools/controllers/RegexController.java b/src/main/java/com/github/reugn/devtools/controllers/RegexController.java new file mode 100644 index 0000000..87a0e22 --- /dev/null +++ b/src/main/java/com/github/reugn/devtools/controllers/RegexController.java @@ -0,0 +1,123 @@ +package com.github.reugn.devtools.controllers; + +import com.github.reugn.devtools.models.RegexResult; +import com.github.reugn.devtools.services.RegexService; +import com.github.reugn.devtools.utils.Elements; +import com.github.reugn.devtools.utils.Logger; +import javafx.event.ActionEvent; +import javafx.fxml.FXML; +import javafx.fxml.Initializable; +import javafx.geometry.Insets; +import javafx.scene.control.Button; +import javafx.scene.control.Label; +import javafx.scene.control.TextArea; +import javafx.scene.control.TextField; +import javafx.scene.input.KeyCode; +import javafx.scene.input.KeyEvent; +import javafx.scene.layout.Border; +import javafx.scene.layout.HBox; +import javafx.scene.paint.Color; +import javafx.util.Pair; +import org.controlsfx.control.CheckComboBox; +import org.fxmisc.richtext.CodeArea; + +import java.net.URL; +import java.util.List; +import java.util.ResourceBundle; + +public class RegexController implements Initializable, Logger { + + @FXML + private TextField regexExpression; + @FXML + private Button regexCalculateButton; + @FXML + private Button regexClearButton; + @FXML + private Label regexMessage; + @FXML + private CodeArea regexTarget; + @FXML + private TextArea regexResult; + @FXML + private Label regexLabel; + @FXML + private CheckComboBox regexFlagsComboBox; + + @Override + public void initialize(URL url, ResourceBundle resourceBundle) { + regexExpression.setPrefWidth(512); + regexTarget.setPrefHeight(256); + HBox.setMargin(regexExpression, new Insets(10, 5, 10, 0)); + HBox.setMargin(regexCalculateButton, new Insets(10, 5, 10, 0)); + HBox.setMargin(regexClearButton, new Insets(10, 5, 10, 0)); + HBox.setMargin(regexFlagsComboBox, new Insets(10, 5, 10, 0)); + + regexLabel.setPadding(new Insets(15, 5, 10, 0)); + regexMessage.setPadding(new Insets(15, 5, 10, 0)); + regexMessage.setTextFill(Color.RED); + + regexFlagsComboBox.getItems().addAll("global", "multiline", "insensitive", "unicode"); + regexFlagsComboBox.setTitle("Flags"); + regexFlagsComboBox.getCheckModel().checkIndices(0); + } + + @FXML + public void handleKeyMatch(KeyEvent keyEvent) { + if (keyEvent.getCode().equals(KeyCode.ENTER)) { + doMatch(); + } + } + + @FXML + public void handleMatch(ActionEvent actionEvent) { + doMatch(); + } + + private void doMatch() { + regexMessage.setText(""); + if (validateInput()) { + try { + RegexResult result = RegexService.match(regexExpression.getText(), regexTarget.getText(), + regexFlagsComboBox.getCheckModel().getCheckedItems()); + regexResult.setText(result.getMatchSummary()); + List> l = result.getFullMatchIndexes(); + if (!l.isEmpty()) { + regexTarget.deselect(); + regexTarget.moveTo(l.get(0).getKey()); + regexTarget.requestFollowCaret(); + regexTarget.selectRange(l.get(0).getKey(), l.get(0).getValue()); + } + } catch (Exception e) { + regexMessage.setText("Invalid regex"); + } + } + } + + private boolean validateInput() { + resetBorders(); + boolean isValid = true; + if (regexExpression.getText().isEmpty()) { + regexExpression.setBorder(Elements.alertBorder); + isValid = false; + } + if (regexTarget.getText().isEmpty()) { + regexTarget.setBorder(Elements.alertBorder); + isValid = false; + } + return isValid; + } + + private void resetBorders() { + regexExpression.setBorder(Border.EMPTY); + regexTarget.setBorder(Elements.codeAreaBorder); + } + + @FXML + public void handleClear(ActionEvent actionEvent) { + resetBorders(); + regexExpression.setText(""); + regexTarget.replaceText(""); + regexResult.setText(""); + } +} diff --git a/src/main/java/com/github/reugn/devtools/controllers/RestAPIController.java b/src/main/java/com/github/reugn/devtools/controllers/RestAPIController.java new file mode 100644 index 0000000..554aa52 --- /dev/null +++ b/src/main/java/com/github/reugn/devtools/controllers/RestAPIController.java @@ -0,0 +1,26 @@ +package com.github.reugn.devtools.controllers; + +import javafx.fxml.FXML; + +import java.net.URL; +import java.util.ResourceBundle; + +public class RestAPIController extends TabPaneController { + + @FXML + private static RestAPIController self; + + @Override + public void initialize(URL url, ResourceBundle resourceBundle) { + self = this; + } + + static RestAPIController instance() { + return self; + } + + @Override + protected String getInnerResource() { + return "/views/rest_api_tab.fxml"; + } +} diff --git a/src/main/java/com/github/reugn/devtools/controllers/RestAPITabController.java b/src/main/java/com/github/reugn/devtools/controllers/RestAPITabController.java new file mode 100644 index 0000000..f6323b2 --- /dev/null +++ b/src/main/java/com/github/reugn/devtools/controllers/RestAPITabController.java @@ -0,0 +1,124 @@ +package com.github.reugn.devtools.controllers; + +import com.github.reugn.devtools.models.RestResponse; +import com.github.reugn.devtools.services.RestService; +import com.github.reugn.devtools.utils.Elements; +import com.github.reugn.devtools.utils.Logger; +import javafx.event.ActionEvent; +import javafx.fxml.FXML; +import javafx.fxml.FXMLLoader; +import javafx.fxml.Initializable; +import javafx.geometry.Insets; +import javafx.scene.Node; +import javafx.scene.control.*; +import javafx.scene.layout.Border; +import javafx.scene.layout.HBox; +import javafx.scene.layout.VBox; +import javafx.util.Pair; + +import java.net.URL; +import java.util.List; +import java.util.Map; +import java.util.ResourceBundle; +import java.util.stream.Collectors; + +public class RestAPITabController implements Initializable, Logger { + + @FXML + private ComboBox methodComboBox; + @FXML + private TextField uriTextField; + @FXML + private Button sendButton; + @FXML + private VBox requestHeadersVBox; + @FXML + private TextArea requestBodyTextArea; + @FXML + private TextArea responseBodyTextArea; + @FXML + private TextArea responseHeadersTextArea; + @FXML + private Label responseStatusLabel; + + @Override + public void initialize(URL url, ResourceBundle resourceBundle) { + uriTextField.setPrefWidth(512); + HBox.setMargin(methodComboBox, new Insets(10, 5, 10, 5)); + HBox.setMargin(uriTextField, new Insets(10, 5, 10, 0)); + HBox.setMargin(sendButton, new Insets(10, 5, 10, 0)); + + methodComboBox.getItems().setAll("GET", "POST", "PUT", "DELETE"); + methodComboBox.setValue("GET"); + + requestHeadersVBox.setPadding(new Insets(10)); + addHeader(); + } + + @FXML + private void handleSend(ActionEvent actionEvent) { + clear(); + if (!validateInput()) return; + RestAPIController.instance().innerTabPane.getSelectionModel().getSelectedItem().setText(tabTitle()); + try { + Map headers = requestHeadersVBox.getChildren().stream().map(n -> { + List h = ((HBox) n).getChildren(); + return new Pair<>(((TextField) h.get(0)).getText(), ((TextField) h.get(1)).getText()); + }).filter(p -> !p.getKey().isEmpty() && !p.getValue().isEmpty()) + .collect(Collectors.toMap(Pair::getKey, Pair::getValue)); + RestResponse response = RestService.request(methodComboBox.getValue(), + uriTextField.getText(), + headers, + requestBodyTextArea.getText()); + responseBodyTextArea.setText(response.getBody()); + responseHeadersTextArea.setText(response.getHeaders()); + responseStatusLabel.setText("STATUS: " + response.getStatus() + ", TIME: " + response.getTime() + "ms"); + } catch (Exception e) { + responseStatusLabel.setText(e.getClass().getName() + ": " + e.getMessage()); + } + } + + private void clear() { + responseStatusLabel.setText(""); + responseBodyTextArea.setText(""); + responseHeadersTextArea.setText(""); + } + + private boolean validateInput() { + uriTextField.setBorder(Border.EMPTY); + if (uriTextField.getText().isEmpty()) { + uriTextField.setBorder(Elements.alertBorder); + return false; + } + return true; + } + + private static int tabTitleLength = 12; + + private String tabTitle() { + String pUrl = uriTextField.getText().length() > tabTitleLength + ? uriTextField.getText().substring(0, tabTitleLength) + : uriTextField.getText(); + return methodComboBox.getValue() + " " + pUrl; + } + + @FXML + private void handleAddHeader(ActionEvent actionEvent) { + addHeader(); + } + + private void addHeader() { + try { + Node n = FXMLLoader.load(this.getClass().getResource("/views/rest_api_header.fxml")); + requestHeadersVBox.getChildren().add(n); + } catch (Exception e) { + } + } + + @FXML + private void handleRemoveHeader(ActionEvent actionEvent) { + int hsize = requestHeadersVBox.getChildren().size(); + if (hsize > 1) + requestHeadersVBox.getChildren().remove(hsize - 1); + } +} diff --git a/src/main/java/com/github/reugn/devtools/controllers/TabPaneController.java b/src/main/java/com/github/reugn/devtools/controllers/TabPaneController.java new file mode 100644 index 0000000..1c4851f --- /dev/null +++ b/src/main/java/com/github/reugn/devtools/controllers/TabPaneController.java @@ -0,0 +1,35 @@ +package com.github.reugn.devtools.controllers; + +import com.github.reugn.devtools.utils.Logger; +import javafx.event.Event; +import javafx.fxml.FXML; +import javafx.fxml.FXMLLoader; +import javafx.fxml.Initializable; +import javafx.scene.control.Tab; +import javafx.scene.control.TabPane; + +abstract public class TabPaneController implements Initializable, Logger { + + @FXML + protected Tab addNewTab; + @FXML + protected TabPane innerTabPane; + + protected abstract String getInnerResource(); + + @FXML + protected void handleNewTab(Event event) { + if (addNewTab.isSelected()) { + int selected = innerTabPane.getSelectionModel().getSelectedIndex(); + int n = innerTabPane.getTabs().size(); + Tab plus = innerTabPane.getTabs().remove(n - 1); + Tab newTab = new Tab("New"); + try { + newTab.setContent(FXMLLoader.load(this.getClass().getResource(getInnerResource()))); + } catch (Exception e) { + } + innerTabPane.getTabs().addAll(newTab, plus); + innerTabPane.getSelectionModel().select(selected); + } + } +} diff --git a/src/main/java/com/github/reugn/devtools/models/RegexResult.java b/src/main/java/com/github/reugn/devtools/models/RegexResult.java new file mode 100644 index 0000000..5a23828 --- /dev/null +++ b/src/main/java/com/github/reugn/devtools/models/RegexResult.java @@ -0,0 +1,23 @@ +package com.github.reugn.devtools.models; + +import javafx.util.Pair; + +import java.util.List; + +public class RegexResult { + private String matchSummary; + private List> fullMatchIndexes; + + public RegexResult(String matchSummary, List> fullMatchIndexes) { + this.matchSummary = matchSummary; + this.fullMatchIndexes = fullMatchIndexes; + } + + public String getMatchSummary() { + return matchSummary; + } + + public List> getFullMatchIndexes() { + return fullMatchIndexes; + } +} diff --git a/src/main/java/com/github/reugn/devtools/models/RestResponse.java b/src/main/java/com/github/reugn/devtools/models/RestResponse.java new file mode 100644 index 0000000..9f0d712 --- /dev/null +++ b/src/main/java/com/github/reugn/devtools/models/RestResponse.java @@ -0,0 +1,31 @@ +package com.github.reugn.devtools.models; + +public class RestResponse { + private int status; + private String body; + private String headers; + private long time; + + public RestResponse(int status, String body, String headers, long time) { + this.status = status; + this.body = body; + this.headers = headers; + this.time = time; + } + + public int getStatus() { + return status; + } + + public String getBody() { + return body; + } + + public String getHeaders() { + return headers; + } + + public long getTime() { + return time; + } +} diff --git a/src/main/java/com/github/reugn/devtools/services/EpochService.java b/src/main/java/com/github/reugn/devtools/services/EpochService.java index 40dcb76..e333960 100644 --- a/src/main/java/com/github/reugn/devtools/services/EpochService.java +++ b/src/main/java/com/github/reugn/devtools/services/EpochService.java @@ -1,13 +1,14 @@ package com.github.reugn.devtools.services; +import com.github.reugn.devtools.utils.Elements; import javafx.scene.control.TextField; -import javafx.scene.layout.*; -import javafx.scene.paint.Color; import java.security.InvalidParameterException; import java.time.*; import java.time.format.DateTimeFormatter; import java.time.format.FormatStyle; +import java.util.TimeZone; +import java.util.concurrent.TimeUnit; public class EpochService { @@ -28,20 +29,30 @@ public static String toHumanEpoch(LocalDateTime dt) { .format(dt.atZone(ZoneId.systemDefault())); String formattedUTC = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.FULL) .format(dt.atZone(ZoneId.systemDefault()).withZoneSameInstant(ZoneOffset.UTC)); - return formatted + "\n" + formattedUTC; + return "Local Time:\n" + formatted + "\nGMT:\n" + formattedUTC; } - public static String toTsEpoch(int year, int month, int dayOfMonth, int hour, int minute, int second) { + public static String toTsEpoch(int year, int month, int dayOfMonth, int hour, int minute, int second, + String timeZone) { StringBuilder buff = new StringBuilder(); - ZoneOffset offset = OffsetDateTime.now().getOffset(); + ZoneOffset offset = OffsetDateTime.now(ZoneId.of(timeZone)).getOffset(); LocalDateTime dt = LocalDateTime.of(year, month, dayOfMonth, hour, minute, second); buff.append("Epoch timestamp: "); buff.append(dt.toInstant(offset).getEpochSecond()).append("\n"); buff.append("Timestamp in milliseconds: "); buff.append(dt.toInstant(offset).toEpochMilli()); + buff.append("\n"); + buff.append("Time Zone: ").append(displayTimeZone(TimeZone.getTimeZone(timeZone))); return buff.toString(); } + private static String displayTimeZone(TimeZone tz) { + long hours = TimeUnit.MILLISECONDS.toHours(tz.getRawOffset()); + long minutes = Math.abs(TimeUnit.MILLISECONDS.toMinutes(tz.getRawOffset()) - TimeUnit.HOURS.toMinutes(hours)); + return hours > 0 ? String.format("(GMT+%d:%02d) %s", hours, minutes, tz.getID()) + : String.format("(GMT%d:%02d) %s", hours, minutes, tz.getID()); + } + public static int validate(TextField f, int min, int max) { int intVal; try { @@ -50,8 +61,7 @@ public static int validate(TextField f, int min, int max) { throw new InvalidParameterException("Invalid input: " + f.getText()); } } catch (Exception e) { - f.setBorder(new Border(new BorderStroke(Color.RED, - BorderStrokeStyle.SOLID, new CornerRadii(3), BorderWidths.DEFAULT))); + f.setBorder(Elements.alertBorder); throw e; } return intVal; diff --git a/src/main/java/com/github/reugn/devtools/services/RegexService.java b/src/main/java/com/github/reugn/devtools/services/RegexService.java new file mode 100644 index 0000000..ce531c9 --- /dev/null +++ b/src/main/java/com/github/reugn/devtools/services/RegexService.java @@ -0,0 +1,43 @@ +package com.github.reugn.devtools.services; + +import com.github.reugn.devtools.models.RegexResult; +import javafx.util.Pair; + +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class RegexService { + + private RegexService() { + } + + public static RegexResult match(String regex, String target, List flagList) { + Pattern pattern = Pattern.compile(regex, calculateFlags(flagList)); + Matcher matcher = pattern.matcher(target); + StringBuilder builder = new StringBuilder(); + boolean isGlobal = flagList.contains("global"); + ArrayList> fullMatch = new ArrayList<>(); + while (matcher.find()) { + for (int i = 0; i <= matcher.groupCount(); i++) { + builder.append(i) + .append(": ") + .append(target, matcher.start(i), matcher.end(i)) + .append("\n"); + } + builder.append("\n"); + fullMatch.add(new Pair<>(matcher.start(0), matcher.end(0))); + if (!isGlobal) break; + } + return new RegexResult(builder.toString(), fullMatch); + } + + private static int calculateFlags(List flagList) { + int flags = 0; + if (flagList.contains("multiline")) flags |= Pattern.MULTILINE; + if (flagList.contains("insensitive")) flags |= Pattern.CASE_INSENSITIVE; + if (flagList.contains("unicode")) flags |= Pattern.UNICODE_CASE; + return flags; + } +} diff --git a/src/main/java/com/github/reugn/devtools/services/RestService.java b/src/main/java/com/github/reugn/devtools/services/RestService.java new file mode 100644 index 0000000..f42caab --- /dev/null +++ b/src/main/java/com/github/reugn/devtools/services/RestService.java @@ -0,0 +1,93 @@ +package com.github.reugn.devtools.services; + +import com.github.reugn.devtools.models.RestResponse; + +import java.io.*; +import java.net.HttpURLConnection; +import java.net.MalformedURLException; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +public class RestService { + + private RestService() { + } + + private static int defaultConnectTimeout = 60000; + + public static RestResponse request(String requestMethod, String uri, Map headers, + String body) throws Exception { + HttpURLConnection conn = (HttpURLConnection) buildURL(uri).openConnection(); + conn.setRequestMethod(requestMethod); + for (Map.Entry h : headers.entrySet()) { + conn.setRequestProperty(h.getKey(), h.getValue()); + } + conn.setConnectTimeout(defaultConnectTimeout); + conn.setInstanceFollowRedirects(false); + + String responseBody = ""; + int status; + long t1 = System.currentTimeMillis(); + if (!body.isEmpty()) { + conn.setDoOutput(true); + setRequestBody(conn.getOutputStream(), body); + } + try { + status = conn.getResponseCode(); + responseBody = getResponseBody(conn.getInputStream()); + } catch (IOException e) { + status = conn.getResponseCode(); + responseBody = getResponseBody(conn.getErrorStream()); + } + long t2 = System.currentTimeMillis() - t1; + + String responseHeaders = getResponseHeaders(conn.getHeaderFields()); + conn.disconnect(); + return new RestResponse(status, responseBody, responseHeaders, t2); + } + + private static void setRequestBody(OutputStream os, String body) throws IOException { + OutputStreamWriter outStreamWriter = new OutputStreamWriter(os, StandardCharsets.UTF_8); + outStreamWriter.write(body); + outStreamWriter.flush(); + outStreamWriter.close(); + os.close(); + } + + private static String getResponseBody(InputStream is) throws IOException { + BufferedReader in = new BufferedReader(new InputStreamReader(is)); + String line; + StringBuilder buff = new StringBuilder(); + while ((line = in.readLine()) != null) { + buff.append(line); + } + in.close(); + return buff.toString(); + } + + private static String getResponseHeaders(Map> fields) { + return fields.entrySet().stream() + .map(e -> { + if (e.getKey() == null) + return String.join(", ", e.getValue()); + else + return e.getKey() + ": " + String.join(", ", e.getValue()); + }) + .collect(Collectors.joining("\n")); + } + + private static URL buildURL(String uri) throws MalformedURLException { + URL url; + try { + url = new URL(uri); + } catch (MalformedURLException e) { + if (uri.startsWith("http")) + throw e; + return buildURL("http://" + uri); + } + return url; + } +} diff --git a/src/main/java/com/github/reugn/devtools/utils/Elements.java b/src/main/java/com/github/reugn/devtools/utils/Elements.java new file mode 100644 index 0000000..df4c047 --- /dev/null +++ b/src/main/java/com/github/reugn/devtools/utils/Elements.java @@ -0,0 +1,13 @@ +package com.github.reugn.devtools.utils; + +import javafx.scene.layout.*; +import javafx.scene.paint.Color; + +public class Elements { + + public static Border alertBorder = new Border(new BorderStroke(Color.rgb(0x80, 0x00, 0x00), + BorderStrokeStyle.SOLID, new CornerRadii(3), BorderWidths.DEFAULT)); + + public static Border codeAreaBorder = new Border(new BorderStroke(Color.rgb(0xb8, 0xb8, 0xb8), + BorderStrokeStyle.SOLID, new CornerRadii(3), BorderWidths.DEFAULT)); +} diff --git a/src/main/java/com/github/reugn/devtools/utils/HttpHeadersTextField.java b/src/main/java/com/github/reugn/devtools/utils/HttpHeadersTextField.java new file mode 100644 index 0000000..893632b --- /dev/null +++ b/src/main/java/com/github/reugn/devtools/utils/HttpHeadersTextField.java @@ -0,0 +1,77 @@ +package com.github.reugn.devtools.utils; + +import com.google.common.net.HttpHeaders; +import javafx.geometry.Side; +import javafx.scene.control.ContextMenu; +import javafx.scene.control.CustomMenuItem; +import javafx.scene.control.Label; +import javafx.scene.control.TextField; + +import java.lang.reflect.Field; +import java.util.LinkedList; +import java.util.List; +import java.util.SortedSet; +import java.util.TreeSet; + +public class HttpHeadersTextField extends TextField { + + private static final SortedSet entries; + private ContextMenu entriesPopup; + private final int maxEntries = 10; + + static { + entries = new TreeSet<>(); + Field[] declaredFields = HttpHeaders.class.getDeclaredFields(); + for (Field field : declaredFields) { + if (java.lang.reflect.Modifier.isStatic(field.getModifiers())) { + try { + entries.add((String) field.get(null)); + } catch (Exception e) { + } + } + } + } + + public HttpHeadersTextField() { + super(); + entriesPopup = new ContextMenu(); + textProperty().addListener((observableValue, s, s2) -> { + if (getText().length() == 0) { + entriesPopup.hide(); + } else { + LinkedList searchResult = new LinkedList<>(entries.subSet(getText(), + getText() + Character.MAX_VALUE)); + if (entries.size() > 0) { + populatePopup(searchResult); + if (!entriesPopup.isShowing()) { + entriesPopup.show(HttpHeadersTextField.this, Side.BOTTOM, 0, 0); + } + } else { + entriesPopup.hide(); + } + } + }); + focusedProperty().addListener((observableValue, aBoolean, aBoolean2) -> entriesPopup.hide()); + } + + public SortedSet getEntries() { + return entries; + } + + private void populatePopup(List searchResult) { + List menuItems = new LinkedList<>(); + int count = Math.min(searchResult.size(), maxEntries); + for (int i = 0; i < count; i++) { + final String result = searchResult.get(i); + Label entryLabel = new Label(result); + CustomMenuItem item = new CustomMenuItem(entryLabel, true); + item.setOnAction(actionEvent -> { + setText(result); + entriesPopup.hide(); + }); + menuItems.add(item); + } + entriesPopup.getItems().clear(); + entriesPopup.getItems().addAll(menuItems); + } +} \ No newline at end of file diff --git a/src/main/resources/css/main.css b/src/main/resources/css/main.css index 1ebba25..7ba06cc 100644 --- a/src/main/resources/css/main.css +++ b/src/main/resources/css/main.css @@ -5,20 +5,43 @@ Label.uc { -fx-font-size: 18px; -fx-font-style: italic; + -fx-font-weight: bold; -fx-text-fill: grey; - -fx-text-alignment: center; - -fx-fill-width: true; } .row { -fx-padding: 5 5 5 5; } +.regexField { + -fx-text-fill: #001a4d; + -fx-font-weight: bold; +} + +CodeArea { + -fx-border-color: #b8b8b8; + -fx-background-color: white; + -fx-background-radius: 3px; + -fx-border-radius: 3px; + -fx-padding: 5; +} + +Label.info { + -fx-font-size: 14px; + -fx-text-fill: #001a4d; + -fx-font-style: italic; + -fx-padding: 5 5 5 5; +} + .resultLabel { -fx-font-size: 14px; -fx-text-fill: grey; } +TabPane.inner { + -fx-border-color: #d3d3d3; +} + Button.refresh { -fx-graphic: url('/images/icons8-refresh-16.png'); } diff --git a/src/main/resources/views/epoch.fxml b/src/main/resources/views/epoch.fxml index ea4b084..79cf2bb 100644 --- a/src/main/resources/views/epoch.fxml +++ b/src/main/resources/views/epoch.fxml @@ -35,6 +35,7 @@