From fadaba9639944f3f9faa2edff01ff3704b4fcda6 Mon Sep 17 00:00:00 2001 From: David Gerber Date: Sat, 11 Nov 2023 15:16:12 +0100 Subject: [PATCH] Feature/atlanta fx (#170) * Add new theme * Add more UI changes Replace fontawesome 4 by iconli + fontawesome 5. Use Atlanta's toolbar instead of the custom one. Add more dark mode fixes. Fix a NPE when running without network connection. * Fix colors in nicknames in the chat for dark mode * Add ways to change themes Also applied many fixes and removed old cruft. * Improve UI Fix "Reply" menu not working in forums. Fix color for JOIN/PART events. * Improve UI Bigger default avatar in the chat. Hyperlinks appear as the description instead of the URL. The URL is shown in the tooltip. Improved the forum message header. Improve the chat listview margins. Markdown links are processed properly (instead of just the URL part). * Add support to past own ID into forum threads * Make chatroom only creatable if there's a room name and a topic * Remove old CSS classes * Fix tests --- .../xeres/app/net/protocol/PeerAddress.java | 5 + ui/build.gradle | 5 +- .../java/io/xeres/ui/JavaFxApplication.java | 19 + .../about/AboutWindowController.java | 10 +- .../ChatRoomCreationWindowController.java | 9 +- .../ui/controller/chat/ChatUserCell.java | 7 +- .../controller/chat/ChatViewController.java | 75 +--- .../forum/ForumEditorViewController.java | 6 +- .../settings/SettingsGeneralController.java | 31 ++ .../java/io/xeres/ui/custom/ChatListCell.java | 19 +- .../java/io/xeres/ui/custom/EditorView.java | 7 + .../io/xeres/ui/custom/ReadOnlyTextField.java | 2 - .../io/xeres/ui/support/chat/ChatLine.java | 18 +- .../xeres/ui/support/chat/ColorGenerator.java | 49 ++- .../ui/support/contentline/ContentUri.java | 9 + .../ui/support/markdown/LinkDetector.java | 22 +- .../ui/support/markdown/MarkdownService.java | 3 +- .../ui/support/markdown/UrlDetector.java | 42 ++ .../io/xeres/ui/support/theme/AppTheme.java | 65 +++ .../io/xeres/ui/support/uri/UriParser.java | 25 +- .../support/util/TextInputControlUtils.java | 111 +++++ .../io/xeres/ui/support/util/UiUtils.java | 26 +- ui/src/main/resources/image/avatar_16.png | Bin 650 -> 0 bytes ui/src/main/resources/image/avatar_32.png | Bin 0 -> 1382 bytes ui/src/main/resources/view/about/about.fxml | 77 ++-- .../view/account/account_creation.fxml | 19 +- .../resources/view/chat/chatroom_create.fxml | 10 +- ui/src/main/resources/view/chat/chatview.fxml | 31 +- .../resources/view/custom/editorview.fxml | 32 +- .../main/resources/view/debug/properties.fxml | 2 +- ui/src/main/resources/view/default.css | 235 +++++++++++ .../resources/view/forum/forum_create.fxml | 6 +- .../resources/view/forum/forumeditorview.fxml | 6 +- .../main/resources/view/forum/forumview.fxml | 27 +- ui/src/main/resources/view/id/rsid_add.fxml | 24 +- ui/src/main/resources/view/javafx-dark.css | 81 ---- ui/src/main/resources/view/javafx.css | 275 ------------- ui/src/main/resources/view/main.fxml | 379 +++++++++--------- .../main/resources/view/profile/profiles.fxml | 6 +- .../resources/view/settings/settings.fxml | 2 +- .../view/settings/settings_general.fxml | 61 +-- .../view/settings/settings_networks.fxml | 253 ++++++------ .../ui/support/chat/ColorGeneratorTest.java | 3 +- .../xeres/ui/support/util/ImageUtilsTest.java | 8 +- 44 files changed, 1121 insertions(+), 981 deletions(-) create mode 100644 ui/src/main/java/io/xeres/ui/support/markdown/UrlDetector.java create mode 100644 ui/src/main/java/io/xeres/ui/support/theme/AppTheme.java create mode 100644 ui/src/main/java/io/xeres/ui/support/util/TextInputControlUtils.java delete mode 100644 ui/src/main/resources/image/avatar_16.png create mode 100644 ui/src/main/resources/image/avatar_32.png create mode 100644 ui/src/main/resources/view/default.css delete mode 100644 ui/src/main/resources/view/javafx-dark.css delete mode 100644 ui/src/main/resources/view/javafx.css diff --git a/app/src/main/java/io/xeres/app/net/protocol/PeerAddress.java b/app/src/main/java/io/xeres/app/net/protocol/PeerAddress.java index 383b95828..3f049977d 100644 --- a/app/src/main/java/io/xeres/app/net/protocol/PeerAddress.java +++ b/app/src/main/java/io/xeres/app/net/protocol/PeerAddress.java @@ -462,6 +462,11 @@ public boolean isHostname() private static boolean isInvalidIpAddress(String address) { + if (address == null) + { + return true; + } + var octets = address.split("\\."); if (octets.length != 4) diff --git a/ui/build.gradle b/ui/build.gradle index 13beef9fe..54a6161af 100644 --- a/ui/build.gradle +++ b/ui/build.gradle @@ -62,10 +62,13 @@ dependencies { implementation 'org.apache.commons:commons-lang3' implementation "org.jsoup:jsoup:$jsoupVersion" implementation "com.github.java-json-tools:json-patch:$jsonPatchVersion" - implementation 'de.jensd:fontawesomefx-fontawesome:4.7.0-9.1.2' implementation 'com.github.sarxos:webcam-capture:0.3.12' implementation "com.google.zxing:javase:$zxingVersion" implementation "net.harawata:appdirs:$appDirsVersion" + implementation 'io.github.mkpaz:atlantafx-base:2.0.1' + implementation platform('org.kordamp.ikonli:ikonli-bom:12.3.1') + implementation 'org.kordamp.ikonli:ikonli-javafx' + implementation 'org.kordamp.ikonli:ikonli-fontawesome5-pack' testImplementation('org.springframework.boot:spring-boot-starter-test') { exclude group: "com.vaadin.external.google", module: "android-json" } diff --git a/ui/src/main/java/io/xeres/ui/JavaFxApplication.java b/ui/src/main/java/io/xeres/ui/JavaFxApplication.java index eedf6bcfa..255fc90e9 100644 --- a/ui/src/main/java/io/xeres/ui/JavaFxApplication.java +++ b/ui/src/main/java/io/xeres/ui/JavaFxApplication.java @@ -21,13 +21,16 @@ import io.xeres.common.mui.MinimalUserInterface; import io.xeres.ui.controller.MainWindowController; +import io.xeres.ui.support.theme.AppTheme; import javafx.application.Application; import javafx.application.HostServices; import javafx.stage.Stage; import org.springframework.boot.builder.SpringApplicationBuilder; import org.springframework.context.ConfigurableApplicationContext; +import java.lang.reflect.InvocationTargetException; import java.util.Objects; +import java.util.prefs.Preferences; public class JavaFxApplication extends Application { @@ -75,11 +78,27 @@ public void start(Stage primaryStage) { hostServices = getHostServices(); + var preferences = Preferences.userRoot().node("Application"); + var theme = preferences.get("Theme", AppTheme.PRIMER_LIGHT.getName()); + setTheme(AppTheme.findByName(theme)); + Objects.requireNonNull(springContext); mainWindowController = springContext.getBean(MainWindowController.class); springContext.publishEvent(new StageReadyEvent(primaryStage)); } + private static void setTheme(AppTheme appTheme) + { + try + { + Application.setUserAgentStylesheet(appTheme.getThemeClass().getDeclaredConstructor().newInstance().getUserAgentStylesheet()); + } + catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) + { + throw new RuntimeException(e); + } + } + @Override public void stop() { diff --git a/ui/src/main/java/io/xeres/ui/controller/about/AboutWindowController.java b/ui/src/main/java/io/xeres/ui/controller/about/AboutWindowController.java index d13934b15..5f4b6b3f8 100644 --- a/ui/src/main/java/io/xeres/ui/controller/about/AboutWindowController.java +++ b/ui/src/main/java/io/xeres/ui/controller/about/AboutWindowController.java @@ -24,7 +24,11 @@ import io.xeres.ui.support.util.UiUtils; import javafx.application.Platform; import javafx.fxml.FXML; -import javafx.scene.control.*; +import javafx.scene.control.Button; +import javafx.scene.control.Hyperlink; +import javafx.scene.control.Label; +import javafx.scene.control.TabPane; +import javafx.scene.text.Text; import net.rgielen.fxweaver.core.FxmlView; import org.springframework.boot.info.BuildProperties; import org.springframework.core.env.Environment; @@ -46,7 +50,7 @@ public class AboutWindowController implements WindowController private TabPane infoPane; @FXML - private TextArea licenseTextArea; + private Text license; @FXML private Label version; @@ -83,7 +87,7 @@ public void initialize() throws IOException { profile.setText("Profiles: " + String.join(", ", environment.getActiveProfiles())); } - licenseTextArea.setText(UiUtils.getResourceFileAsString(getClass().getResourceAsStream("/LICENSE"))); + license.setText(UiUtils.getResourceFileAsString(getClass().getResourceAsStream("/LICENSE"))); twemoji.setOnAction(event -> JavaFxApplication.openUrl("https://github.com/twitter/twemoji")); twemojiLicense.setOnAction(event -> JavaFxApplication.openUrl("https://creativecommons.org/licenses/by/4.0/")); diff --git a/ui/src/main/java/io/xeres/ui/controller/chat/ChatRoomCreationWindowController.java b/ui/src/main/java/io/xeres/ui/controller/chat/ChatRoomCreationWindowController.java index d85c84997..6636baa8d 100644 --- a/ui/src/main/java/io/xeres/ui/controller/chat/ChatRoomCreationWindowController.java +++ b/ui/src/main/java/io/xeres/ui/controller/chat/ChatRoomCreationWindowController.java @@ -69,8 +69,8 @@ public ChatRoomCreationWindowController(ChatClient chatClient, ResourceBundle bu @Override public void initialize() { - roomName.textProperty().addListener(observable -> createButton.setDisable(roomName.getText().isBlank())); - topic.textProperty().addListener(observable -> createButton.setDisable(topic.getText().isBlank())); + roomName.textProperty().addListener(observable -> checkCreatable()); + topic.textProperty().addListener(observable -> checkCreatable()); visibility.setItems(FXCollections.observableArrayList(bundle.getString("enum.roomtype.public"), bundle.getString("enum.roomtype.private"))); visibility.getSelectionModel().select(0); @@ -83,4 +83,9 @@ public void initialize() .subscribe()); cancelButton.setOnAction(UiUtils::closeWindow); } + + private void checkCreatable() + { + createButton.setDisable(roomName.getText().isBlank() || topic.getText().isBlank()); + } } diff --git a/ui/src/main/java/io/xeres/ui/controller/chat/ChatUserCell.java b/ui/src/main/java/io/xeres/ui/controller/chat/ChatUserCell.java index 34b7d6540..5b0bc0284 100644 --- a/ui/src/main/java/io/xeres/ui/controller/chat/ChatUserCell.java +++ b/ui/src/main/java/io/xeres/ui/controller/chat/ChatUserCell.java @@ -28,7 +28,8 @@ public class ChatUserCell extends ListCell { - private static final ImageView defaultImage = new ImageView("/image/avatar_16.png"); + private static final int DEFAULT_AVATAR_SIZE = 32; + private static final ImageView defaultImage = new ImageView("/image/avatar_" + DEFAULT_AVATAR_SIZE + ".png"); public ChatUserCell() { @@ -53,8 +54,8 @@ private ImageView getAvatarImage(ChatRoomUser item) if (item.image() != null) { image = new ImageView(item.image().getImage()); - image.setFitWidth(16.0); - image.setFitHeight(16.0); + image.setFitWidth(DEFAULT_AVATAR_SIZE); + image.setFitHeight(DEFAULT_AVATAR_SIZE); } else { diff --git a/ui/src/main/java/io/xeres/ui/controller/chat/ChatViewController.java b/ui/src/main/java/io/xeres/ui/controller/chat/ChatViewController.java index 2f9491f39..f3f414170 100644 --- a/ui/src/main/java/io/xeres/ui/controller/chat/ChatViewController.java +++ b/ui/src/main/java/io/xeres/ui/controller/chat/ChatViewController.java @@ -19,11 +19,8 @@ package io.xeres.ui.controller.chat; -import io.xeres.common.AppName; import io.xeres.common.i18n.I18nUtils; import io.xeres.common.message.chat.*; -import io.xeres.common.rest.location.RSIdResponse; -import io.xeres.common.rsid.Type; import io.xeres.ui.client.ChatClient; import io.xeres.ui.client.LocationClient; import io.xeres.ui.client.ProfileClient; @@ -35,12 +32,12 @@ import io.xeres.ui.support.markdown.MarkdownService; import io.xeres.ui.support.tray.TrayService; import io.xeres.ui.support.util.ImageUtils; +import io.xeres.ui.support.util.TextInputControlUtils; import io.xeres.ui.support.util.UiUtils; import io.xeres.ui.support.window.WindowManager; import javafx.animation.KeyFrame; import javafx.animation.Timeline; import javafx.application.Platform; -import javafx.beans.binding.Bindings; import javafx.collections.ObservableList; import javafx.fxml.FXML; import javafx.fxml.FXMLLoader; @@ -55,8 +52,6 @@ import org.springframework.stereotype.Component; import java.io.IOException; -import java.net.URI; -import java.net.URLEncoder; import java.text.MessageFormat; import java.time.Duration; import java.time.Instant; @@ -66,9 +61,7 @@ import java.util.function.Consumer; import java.util.stream.Collectors; -import static io.xeres.common.dto.location.LocationConstants.OWN_LOCATION_ID; import static io.xeres.common.message.chat.ChatConstants.TYPING_NOTIFICATION_DELAY; -import static java.nio.charset.StandardCharsets.UTF_8; import static org.apache.commons.lang3.ObjectUtils.isEmpty; import static org.apache.commons.lang3.StringUtils.isNotBlank; @@ -240,7 +233,7 @@ public void initialize() throws IOException ae -> typingNotification.setText(""))); send.addEventHandler(KeyEvent.KEY_PRESSED, this::handleInputKeys); - send.setContextMenu(createChatInputContextMenu(send)); + send.setContextMenu(TextInputControlUtils.createInputContextMenu(send, locationClient)); invite.setOnAction(event -> windowManager.openInvite(UiUtils.getWindow(event), selectedRoom.getId())); @@ -640,70 +633,6 @@ private void setPreviewGroupVisibility(boolean visible) previewGroup.setManaged(visible); } - private ContextMenu createChatInputContextMenu(TextInputControl textInputControl) - { - var contextMenu = new ContextMenu(); - - contextMenu.getItems().addAll(createDefaultChatInputMenuItems(textInputControl)); - var pasteId = new MenuItem(bundle.getString("chat.room.input.paste-id")); - pasteId.setOnAction(event -> appendOwnId(textInputControl)); - contextMenu.getItems().addAll(new SeparatorMenuItem(), pasteId); - return contextMenu; - } - - private void appendOwnId(TextInputControl textInputControl) - { - var rsIdResponse = locationClient.getRSId(OWN_LOCATION_ID, Type.CERTIFICATE); - rsIdResponse.subscribe(reply -> Platform.runLater(() -> textInputControl.appendText(buildRetroshareUrl(reply)))); - } - - private String buildRetroshareUrl(RSIdResponse rsIdResponse) - { - var uri = URI.create("retroshare://certificate?" + - "radix=" + URLEncoder.encode(rsIdResponse.rsId().replace("\n", ""), UTF_8) + // Removing the '\n' is in case this is a certificate which is sliced for presentation - "&name=" + URLEncoder.encode(rsIdResponse.name(), UTF_8) + - "&location=" + URLEncoder.encode(rsIdResponse.location(), UTF_8)); - return "" + AppName.NAME + " Certificate (" + rsIdResponse.name() + ", @" + rsIdResponse.location() + ")"; - } - - private List createDefaultChatInputMenuItems(TextInputControl textInputControl) - { - var undo = new MenuItem(bundle.getString("chat.room.input.undo")); - undo.setOnAction(event -> textInputControl.undo()); - - var redo = new MenuItem(bundle.getString("chat.room.input.redo")); - redo.setOnAction(event -> textInputControl.redo()); - - var cut = new MenuItem(bundle.getString("chat.room.input.cut")); - cut.setOnAction(event -> textInputControl.cut()); - - var copy = new MenuItem(bundle.getString("chat.room.input.copy")); - copy.setOnAction(event -> textInputControl.copy()); - - var paste = new MenuItem(bundle.getString("chat.room.input.paste")); - paste.setOnAction(event -> textInputControl.paste()); - - var delete = new MenuItem(bundle.getString("chat.room.input.delete")); - delete.setOnAction(event -> textInputControl.deleteText(textInputControl.getSelection())); - - var selectAll = new MenuItem(bundle.getString("chat.room.input.select-all")); - selectAll.setOnAction(event -> textInputControl.selectAll()); - - var emptySelection = Bindings.createBooleanBinding(() -> textInputControl.getSelection().getLength() == 0, textInputControl.selectionProperty()); - - cut.disableProperty().bind(emptySelection); - copy.disableProperty().bind(emptySelection); - delete.disableProperty().bind(emptySelection); - - var canUndo = Bindings.createBooleanBinding(() -> !textInputControl.isUndoable(), textInputControl.undoableProperty()); - var canRedo = Bindings.createBooleanBinding(() -> !textInputControl.isRedoable(), textInputControl.redoableProperty()); - - undo.disableProperty().bind(canUndo); - redo.disableProperty().bind(canRedo); - - return List.of(undo, redo, cut, copy, paste, delete, new SeparatorMenuItem(), selectAll); - } - public void openInvite(long chatRoomId, ChatRoomInviteEvent event) { Platform.runLater(() -> UiUtils.alertConfirm(MessageFormat.format(bundle.getString("chat.room.invite.request"), event.getLocationId(), event.getRoomName(), event.getRoomTopic()), diff --git a/ui/src/main/java/io/xeres/ui/controller/forum/ForumEditorViewController.java b/ui/src/main/java/io/xeres/ui/controller/forum/ForumEditorViewController.java index a905c0c88..c26c97524 100644 --- a/ui/src/main/java/io/xeres/ui/controller/forum/ForumEditorViewController.java +++ b/ui/src/main/java/io/xeres/ui/controller/forum/ForumEditorViewController.java @@ -22,6 +22,7 @@ import io.xeres.common.message.forum.ForumMessage; import io.xeres.common.rest.forum.PostRequest; import io.xeres.ui.client.ForumClient; +import io.xeres.ui.client.LocationClient; import io.xeres.ui.controller.WindowController; import io.xeres.ui.custom.EditorView; import io.xeres.ui.support.util.UiUtils; @@ -55,10 +56,12 @@ public class ForumEditorViewController implements WindowController private PostRequest postRequest; private final ForumClient forumClient; + private final LocationClient locationClient; - public ForumEditorViewController(ForumClient forumClient) + public ForumEditorViewController(ForumClient forumClient, LocationClient locationClient) { this.forumClient = forumClient; + this.locationClient = locationClient; } @Override @@ -67,6 +70,7 @@ public void initialize() throws IOException Platform.runLater(() -> title.requestFocus()); editorView.lengthProperty.addListener((observable, oldValue, newValue) -> checkSendable((Integer) newValue)); + editorView.setInputContextMenu(locationClient); title.setOnKeyTyped(event -> checkSendable(editorView.lengthProperty.getValue())); send.setOnAction(event -> postMessage()); diff --git a/ui/src/main/java/io/xeres/ui/controller/settings/SettingsGeneralController.java b/ui/src/main/java/io/xeres/ui/controller/settings/SettingsGeneralController.java index d9662af3f..a931990a7 100644 --- a/ui/src/main/java/io/xeres/ui/controller/settings/SettingsGeneralController.java +++ b/ui/src/main/java/io/xeres/ui/controller/settings/SettingsGeneralController.java @@ -22,17 +22,28 @@ import io.xeres.common.rest.config.Capabilities; import io.xeres.ui.client.ConfigClient; import io.xeres.ui.model.settings.Settings; +import io.xeres.ui.support.theme.AppTheme; +import javafx.application.Application; import javafx.application.Platform; +import javafx.beans.value.ObservableValue; import javafx.fxml.FXML; import javafx.scene.control.CheckBox; +import javafx.scene.control.ChoiceBox; import javafx.scene.control.Label; import net.rgielen.fxweaver.core.FxmlView; import org.springframework.stereotype.Component; +import java.lang.reflect.InvocationTargetException; +import java.util.Arrays; +import java.util.prefs.Preferences; + @Component @FxmlView(value = "/view/settings/settings_general.fxml") public class SettingsGeneralController implements SettingsController { + @FXML + private ChoiceBox themeSelector; + @FXML private CheckBox autoStartEnabled; @@ -48,9 +59,29 @@ public SettingsGeneralController(ConfigClient configClient) this.configClient = configClient; } + private static void changeTheme(ObservableValue observable, AppTheme oldValue, AppTheme newValue) + { + try + { + Application.setUserAgentStylesheet(newValue.getThemeClass().getDeclaredConstructor().newInstance().getUserAgentStylesheet()); + var preferences = Preferences.userRoot().node("Application"); + preferences.put("Theme", newValue.getName()); + } + catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) + { + throw new RuntimeException(e); + } + } + @Override public void initialize() { + themeSelector.getItems().addAll(Arrays.stream(AppTheme.values()).toList()); + var preferences = Preferences.userRoot().node("Application"); + var theme = preferences.get("Theme", AppTheme.PRIMER_LIGHT.getName()); + themeSelector.getSelectionModel().select(AppTheme.findByName(theme)); + themeSelector.getSelectionModel().selectedItemProperty().addListener(SettingsGeneralController::changeTheme); + configClient.getCapabilities() .doOnSuccess(capabilities -> Platform.runLater(() -> { if (capabilities.contains(Capabilities.AUTOSTART)) diff --git a/ui/src/main/java/io/xeres/ui/custom/ChatListCell.java b/ui/src/main/java/io/xeres/ui/custom/ChatListCell.java index ca89a5001..a47dc4c53 100644 --- a/ui/src/main/java/io/xeres/ui/custom/ChatListCell.java +++ b/ui/src/main/java/io/xeres/ui/custom/ChatListCell.java @@ -20,15 +20,17 @@ package io.xeres.ui.custom; import io.xeres.ui.support.chat.ChatLine; +import io.xeres.ui.support.chat.ColorGenerator; import io.xeres.ui.support.contentline.Content; +import javafx.css.PseudoClass; import javafx.scene.control.Label; -import javafx.scene.text.Text; import javafx.scene.text.TextFlow; import org.fxmisc.flowless.Cell; import java.time.ZoneId; import java.time.format.DateTimeFormatter; import java.time.format.FormatStyle; +import java.util.List; import java.util.Locale; public class ChatListCell implements Cell @@ -37,6 +39,10 @@ public class ChatListCell implements Cell .withLocale(Locale.ROOT) .withZone(ZoneId.systemDefault()); + private static final PseudoClass passivePseudoClass = PseudoClass.getPseudoClass("passive"); + + private static final List allColors = ColorGenerator.getAllColors(); + private final TextFlow content; private final Label time; private final Label action; @@ -86,15 +92,20 @@ public void updateItem(ChatLine line) time.setText(formatter.format(line.getInstant())); action.setText(line.getAction()); - action.setTextFill(line.getNicknameColor()); + var nicknameColor = line.getNicknameColor(); + action.getStyleClass().removeAll(allColors); + if (nicknameColor != null) + { + action.getStyleClass().add(nicknameColor); + } var nodes = line.getChatContents().stream() .map(Content::getNode) .toList(); - if (nodes.size() == 1 && nodes.get(0) instanceof Text text) // XXX: check if that works for single URLs.. + if (!isComplex) { - text.setFill(line.getContentColor()); + content.pseudoClassStateChanged(passivePseudoClass, !line.isActiveAction()); } content.getChildren().addAll(nodes); diff --git a/ui/src/main/java/io/xeres/ui/custom/EditorView.java b/ui/src/main/java/io/xeres/ui/custom/EditorView.java index 673ef0654..edeaffa95 100644 --- a/ui/src/main/java/io/xeres/ui/custom/EditorView.java +++ b/ui/src/main/java/io/xeres/ui/custom/EditorView.java @@ -20,7 +20,9 @@ package io.xeres.ui.custom; import io.xeres.common.i18n.I18nUtils; +import io.xeres.ui.client.LocationClient; import io.xeres.ui.support.util.ImageUtils; +import io.xeres.ui.support.util.TextInputControlUtils; import io.xeres.ui.support.util.UiUtils; import javafx.beans.property.ReadOnlyIntegerWrapper; import javafx.fxml.FXML; @@ -127,6 +129,11 @@ public void setReply(String reply) editor.requestFocus(); } + public void setInputContextMenu(LocationClient locationClient) + { + editor.setContextMenu(TextInputControlUtils.createInputContextMenu(editor, locationClient)); + } + private void surround(String text) { var selection = editor.getSelection(); diff --git a/ui/src/main/java/io/xeres/ui/custom/ReadOnlyTextField.java b/ui/src/main/java/io/xeres/ui/custom/ReadOnlyTextField.java index d79b0bf3d..b90cf7eeb 100644 --- a/ui/src/main/java/io/xeres/ui/custom/ReadOnlyTextField.java +++ b/ui/src/main/java/io/xeres/ui/custom/ReadOnlyTextField.java @@ -52,8 +52,6 @@ public ReadOnlyTextField(String text) private void init() { - getStyleClass().add("text-field-readonly"); - setOnMouseClicked(event -> selectAll()); setContextMenu(createContextMenu()); diff --git a/ui/src/main/java/io/xeres/ui/support/chat/ChatLine.java b/ui/src/main/java/io/xeres/ui/support/chat/ChatLine.java index 8324549fc..e8b704a88 100644 --- a/ui/src/main/java/io/xeres/ui/support/chat/ChatLine.java +++ b/ui/src/main/java/io/xeres/ui/support/chat/ChatLine.java @@ -22,7 +22,6 @@ import io.xeres.common.id.GxsId; import io.xeres.ui.support.contentline.Content; import io.xeres.ui.support.contentline.ContentText; -import javafx.scene.paint.Color; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -70,23 +69,24 @@ public boolean hasSaid(GxsId gxsId) return action.getType() == ChatAction.Type.SAY && gxsId.toString().equals(action.getGxsId()); } - public Color getNicknameColor() + public String getNicknameColor() { return switch (action.getType()) { - case JOIN, LEAVE, TIMEOUT -> Color.GRAY; - case ACTION, SAY_OWN -> Color.BLACK; case SAY -> ColorGenerator.generateColor(action.getGxsId() != null ? action.getGxsId() : action.getNickname()); + default -> null; }; } - public Color getContentColor() + public boolean isActiveAction() { return switch (action.getType()) - { - case JOIN, LEAVE, TIMEOUT -> Color.GRAY; - case SAY, SAY_OWN, ACTION -> Color.BLACK; - }; + { + case JOIN, LEAVE, TIMEOUT: + yield false; + case SAY, SAY_OWN, ACTION: + yield true; + }; } public List getChatContents() diff --git a/ui/src/main/java/io/xeres/ui/support/chat/ColorGenerator.java b/ui/src/main/java/io/xeres/ui/support/chat/ColorGenerator.java index 6734fce61..fbae855f2 100644 --- a/ui/src/main/java/io/xeres/ui/support/chat/ColorGenerator.java +++ b/ui/src/main/java/io/xeres/ui/support/chat/ColorGenerator.java @@ -19,8 +19,8 @@ package io.xeres.ui.support.chat; -import javafx.scene.paint.Color; - +import java.util.Arrays; +import java.util.List; import java.util.Objects; public final class ColorGenerator @@ -36,39 +36,44 @@ private ColorGenerator() */ private enum ColorSpec { - COLOR_00(Color.rgb(204, 0, 0)), - COLOR_01(Color.rgb(0, 108, 173)), - COLOR_02(Color.rgb(77, 153, 0)), - COLOR_03(Color.rgb(102, 0, 204)), - COLOR_04(Color.rgb(166, 125, 0)), - COLOR_05(Color.rgb(0, 153, 39)), - COLOR_06(Color.rgb(0, 48, 192)), - COLOR_07(Color.rgb(204, 0, 154)), - COLOR_08(Color.rgb(185, 70, 0)), - COLOR_09(Color.rgb(134, 153, 0)), - COLOR_10(Color.rgb(20, 153, 0)), - COLOR_11(Color.rgb(0, 153, 96)), - COLOR_12(Color.rgb(0, 108, 173)), - COLOR_13(Color.rgb(0, 153, 204)), - COLOR_14(Color.rgb(179, 0, 204)), - COLOR_15(Color.rgb(204, 0, 77)); + COLOR_00("color-00"), + COLOR_01("color-01"), + COLOR_02("color-02"), + COLOR_03("color-03"), + COLOR_04("color-04"), + COLOR_05("color-05"), + COLOR_06("color-06"), + COLOR_07("color-07"), + COLOR_08("color-08"), + COLOR_09("color-09"), + COLOR_10("color-10"), + COLOR_11("color-11"), + COLOR_12("color-12"), + COLOR_13("color-13"), + COLOR_14("color-14"), + COLOR_15("color-15"); - private final Color color; + private final String color; - ColorSpec(Color color) + ColorSpec(String color) { this.color = color; } - public Color getColor() + public String getColor() { return color; } } - public static Color generateColor(String s) + public static String generateColor(String s) { Objects.requireNonNull(s); return ColorSpec.values()[Math.floorMod(s.hashCode(), ColorSpec.values().length)].getColor(); } + + public static List getAllColors() + { + return Arrays.stream(ColorSpec.values()).map(ColorSpec::getColor).toList(); + } } diff --git a/ui/src/main/java/io/xeres/ui/support/contentline/ContentUri.java b/ui/src/main/java/io/xeres/ui/support/contentline/ContentUri.java index 54d47a065..f23c4bff0 100644 --- a/ui/src/main/java/io/xeres/ui/support/contentline/ContentUri.java +++ b/ui/src/main/java/io/xeres/ui/support/contentline/ContentUri.java @@ -20,6 +20,7 @@ package io.xeres.ui.support.contentline; import io.xeres.ui.JavaFxApplication; +import io.xeres.ui.support.util.TooltipUtils; import javafx.scene.Node; import javafx.scene.control.Hyperlink; @@ -35,9 +36,17 @@ public ContentUri(String uri) node.setOnAction(event -> JavaFxApplication.openUrl(appendMailToIfNeeded(node.getText()))); } + public ContentUri(String uri, String description) + { + node = new Hyperlink(description); + TooltipUtils.install(node, uri); + node.setOnAction(event -> JavaFxApplication.openUrl(appendMailToIfNeeded(uri))); + } + public ContentUri(String uri, String description, Consumer action) { node = new Hyperlink(description); + TooltipUtils.install(node, uri); node.setOnAction(event -> action.accept(uri)); } diff --git a/ui/src/main/java/io/xeres/ui/support/markdown/LinkDetector.java b/ui/src/main/java/io/xeres/ui/support/markdown/LinkDetector.java index 74b578df7..7f4313722 100644 --- a/ui/src/main/java/io/xeres/ui/support/markdown/LinkDetector.java +++ b/ui/src/main/java/io/xeres/ui/support/markdown/LinkDetector.java @@ -19,24 +19,36 @@ package io.xeres.ui.support.markdown; -import io.xeres.ui.support.contentline.ContentUri; +import io.xeres.ui.support.uri.UriParser; import java.util.regex.Pattern; class LinkDetector implements MarkdownDetector { - private static final Pattern URL_PATTERN = Pattern.compile("\\b(?(?:https?|ftps?)://[-A-Z0-9+&@#/%?=~_|!:,.;]*[-A-Z0-9+&@#/%=~_|])|(?[0-9A-Z._+\\-=]+@[0-9a-z\\-]+\\.[a-z]{2,})", Pattern.CASE_INSENSITIVE); + private static final Pattern LINK_PATTERN = Pattern.compile("\\[.{0,256}]\\(.{0,2048}\\)"); // Large URL @Override public boolean isPossibly(String line) { - return line.contains("http") || line.contains("ftp") || line.contains("@"); + return line.contains("["); } @Override public void process(Context context, String line) { - MarkdownService.processPattern(URL_PATTERN, context, line, - (s, groupName) -> context.addContent(new ContentUri(s))); + MarkdownService.processPattern(LINK_PATTERN, context, line, + (s, groupName) -> context.addContent(UriParser.parse(getUrl(s), getDescription(s)))); + } + + private static String getUrl(String s) + { + var index = s.lastIndexOf("("); + return s.substring(index + 1, s.length() - 1); // skip the ")" at the end + } + + private static String getDescription(String s) + { + var index = s.indexOf("]"); + return s.substring(1, index - 1); } } diff --git a/ui/src/main/java/io/xeres/ui/support/markdown/MarkdownService.java b/ui/src/main/java/io/xeres/ui/support/markdown/MarkdownService.java index 41b410477..e3087bb3e 100644 --- a/ui/src/main/java/io/xeres/ui/support/markdown/MarkdownService.java +++ b/ui/src/main/java/io/xeres/ui/support/markdown/MarkdownService.java @@ -34,8 +34,9 @@ public MarkdownService(EmojiService emojiService) substringDetectors.add(new CodeDetector()); substringDetectors.add(new EmphasisDetector()); - substringDetectors.add(new HrefDetector()); substringDetectors.add(new LinkDetector()); + substringDetectors.add(new HrefDetector()); + substringDetectors.add(new UrlDetector()); substringDetectors.add(new ImageDetector()); if (emojiService.isColoredEmojis()) { diff --git a/ui/src/main/java/io/xeres/ui/support/markdown/UrlDetector.java b/ui/src/main/java/io/xeres/ui/support/markdown/UrlDetector.java new file mode 100644 index 000000000..e4e9fb3e1 --- /dev/null +++ b/ui/src/main/java/io/xeres/ui/support/markdown/UrlDetector.java @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2023 by David Gerber - https://zapek.com + * + * This file is part of Xeres. + * + * Xeres is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Xeres is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Xeres. If not, see . + */ + +package io.xeres.ui.support.markdown; + +import io.xeres.ui.support.contentline.ContentUri; + +import java.util.regex.Pattern; + +class UrlDetector implements MarkdownDetector +{ + private static final Pattern URL_PATTERN = Pattern.compile("\\b(?(?:https?|ftps?)://[-A-Z0-9+&@#/%?=~_|!:,.;]*[-A-Z0-9+&@#/%=~_|])|(?[0-9A-Z._+\\-=]+@[0-9a-z\\-]+\\.[a-z]{2,})", Pattern.CASE_INSENSITIVE); + + @Override + public boolean isPossibly(String line) + { + return line.contains("http") || line.contains("ftp") || line.contains("@"); + } + + @Override + public void process(Context context, String line) + { + MarkdownService.processPattern(URL_PATTERN, context, line, + (s, groupName) -> context.addContent(new ContentUri(s))); + } +} diff --git a/ui/src/main/java/io/xeres/ui/support/theme/AppTheme.java b/ui/src/main/java/io/xeres/ui/support/theme/AppTheme.java new file mode 100644 index 000000000..566bde3be --- /dev/null +++ b/ui/src/main/java/io/xeres/ui/support/theme/AppTheme.java @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2023 by David Gerber - https://zapek.com + * + * This file is part of Xeres. + * + * Xeres is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Xeres is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Xeres. If not, see . + */ + +package io.xeres.ui.support.theme; + +import atlantafx.base.theme.*; + +import java.util.Arrays; + +public enum AppTheme +{ + PRIMER_LIGHT("Primer Light", PrimerLight.class), + PRIMER_DARK("Primer Dark", PrimerDark.class), + NORD_LIGHT("Nord Light", NordLight.class), + NORD_DARK("Nord Dark", NordDark.class), + CUPERTINO_LIGHT("Cupertino Light", CupertinoLight.class), + CUPERTINO_DARK("Cupertino Dark", CupertinoDark.class), + DRACULA("Dracula", Dracula.class); + + private final String name; + private final Class themeClass; + + AppTheme(String name, Class themeClass) + { + this.name = name; + this.themeClass = themeClass; + } + + public String getName() + { + return name; + } + + public Class getThemeClass() + { + return themeClass; + } + + public static AppTheme findByName(String name) + { + return Arrays.stream(values()).filter(appTheme -> appTheme.getName().equals(name)).findFirst().orElse(PRIMER_LIGHT); + } + + @Override + public String toString() + { + return name; + } +} diff --git a/ui/src/main/java/io/xeres/ui/support/uri/UriParser.java b/ui/src/main/java/io/xeres/ui/support/uri/UriParser.java index 70dc982e9..12e0e44f8 100644 --- a/ui/src/main/java/io/xeres/ui/support/uri/UriParser.java +++ b/ui/src/main/java/io/xeres/ui/support/uri/UriParser.java @@ -73,21 +73,26 @@ public static Content parse(String href, String text) { var uri = new URI(href); var contentParserMap = contentParsers.get(uri.getScheme()); - if (contentParserMap == null) + if (contentParserMap != null) { - return ContentText.EMPTY; + var contentParser = contentParserMap.get(uri.getAuthority()); + + if (contentParser != null) + { + var uriComponents = UriComponentsBuilder.fromPath(uri.getPath()) + .query(uri.getQuery()) + .build(); + + return contentParser.parse(uriComponents, text); + } } - var contentParser = contentParserMap.get(uri.getAuthority()); - if (contentParser != null) + // If we don't know the URL, delegate to the OS + if (isBlank(text)) { - var uriComponents = UriComponentsBuilder.fromPath(uri.getPath()) - .query(uri.getQuery()) - .build(); - - return contentParser.parse(uriComponents, text); + return new ContentUri(uri.toString()); } - return new ContentUri(uri.toString()); + return new ContentUri(uri.toString(), text); } catch (URISyntaxException e) { diff --git a/ui/src/main/java/io/xeres/ui/support/util/TextInputControlUtils.java b/ui/src/main/java/io/xeres/ui/support/util/TextInputControlUtils.java new file mode 100644 index 000000000..e16962841 --- /dev/null +++ b/ui/src/main/java/io/xeres/ui/support/util/TextInputControlUtils.java @@ -0,0 +1,111 @@ +/* + * Copyright (c) 2023 by David Gerber - https://zapek.com + * + * This file is part of Xeres. + * + * Xeres is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Xeres is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Xeres. If not, see . + */ + +package io.xeres.ui.support.util; + +import io.xeres.common.AppName; +import io.xeres.common.i18n.I18nUtils; +import io.xeres.common.rest.location.RSIdResponse; +import io.xeres.common.rsid.Type; +import io.xeres.ui.client.LocationClient; +import javafx.application.Platform; +import javafx.beans.binding.Bindings; +import javafx.scene.control.ContextMenu; +import javafx.scene.control.MenuItem; +import javafx.scene.control.SeparatorMenuItem; +import javafx.scene.control.TextInputControl; + +import java.net.URI; +import java.net.URLEncoder; +import java.util.List; + +import static io.xeres.common.dto.location.LocationConstants.OWN_LOCATION_ID; +import static java.nio.charset.StandardCharsets.UTF_8; + +public final class TextInputControlUtils +{ + private TextInputControlUtils() + { + throw new UnsupportedOperationException("Utility class"); + } + + public static ContextMenu createInputContextMenu(TextInputControl textInputControl, LocationClient locationClient) + { + var contextMenu = new ContextMenu(); + + contextMenu.getItems().addAll(createDefaultChatInputMenuItems(textInputControl)); + var pasteId = new MenuItem(I18nUtils.getString("chat.room.input.paste-id")); + pasteId.setOnAction(event -> appendOwnId(textInputControl, locationClient)); + contextMenu.getItems().addAll(new SeparatorMenuItem(), pasteId); + return contextMenu; + } + + private static void appendOwnId(TextInputControl textInputControl, LocationClient locationClient) + { + var rsIdResponse = locationClient.getRSId(OWN_LOCATION_ID, Type.CERTIFICATE); + rsIdResponse.subscribe(reply -> Platform.runLater(() -> textInputControl.appendText(buildRetroshareUrl(reply)))); + } + + private static String buildRetroshareUrl(RSIdResponse rsIdResponse) + { + var uri = URI.create("retroshare://certificate?" + + "radix=" + URLEncoder.encode(rsIdResponse.rsId().replace("\n", ""), UTF_8) + // Removing the '\n' is in case this is a certificate which is sliced for presentation + "&name=" + URLEncoder.encode(rsIdResponse.name(), UTF_8) + + "&location=" + URLEncoder.encode(rsIdResponse.location(), UTF_8)); + return "" + AppName.NAME + " Certificate (" + rsIdResponse.name() + ", @" + rsIdResponse.location() + ")"; + } + + private static List createDefaultChatInputMenuItems(TextInputControl textInputControl) + { + var undo = new MenuItem(I18nUtils.getString("chat.room.input.undo")); + undo.setOnAction(event -> textInputControl.undo()); + + var redo = new MenuItem(I18nUtils.getString("chat.room.input.redo")); + redo.setOnAction(event -> textInputControl.redo()); + + var cut = new MenuItem(I18nUtils.getString("chat.room.input.cut")); + cut.setOnAction(event -> textInputControl.cut()); + + var copy = new MenuItem(I18nUtils.getString("chat.room.input.copy")); + copy.setOnAction(event -> textInputControl.copy()); + + var paste = new MenuItem(I18nUtils.getString("chat.room.input.paste")); + paste.setOnAction(event -> textInputControl.paste()); + + var delete = new MenuItem(I18nUtils.getString("chat.room.input.delete")); + delete.setOnAction(event -> textInputControl.deleteText(textInputControl.getSelection())); + + var selectAll = new MenuItem(I18nUtils.getString("chat.room.input.select-all")); + selectAll.setOnAction(event -> textInputControl.selectAll()); + + var emptySelection = Bindings.createBooleanBinding(() -> textInputControl.getSelection().getLength() == 0, textInputControl.selectionProperty()); + + cut.disableProperty().bind(emptySelection); + copy.disableProperty().bind(emptySelection); + delete.disableProperty().bind(emptySelection); + + var canUndo = Bindings.createBooleanBinding(() -> !textInputControl.isUndoable(), textInputControl.undoableProperty()); + var canRedo = Bindings.createBooleanBinding(() -> !textInputControl.isRedoable(), textInputControl.redoableProperty()); + + undo.disableProperty().bind(canUndo); + redo.disableProperty().bind(canRedo); + + return List.of(undo, redo, cut, copy, paste, delete, new SeparatorMenuItem(), selectAll); + } +} diff --git a/ui/src/main/java/io/xeres/ui/support/util/UiUtils.java b/ui/src/main/java/io/xeres/ui/support/util/UiUtils.java index 2f8fb377a..07bdba0a7 100644 --- a/ui/src/main/java/io/xeres/ui/support/util/UiUtils.java +++ b/ui/src/main/java/io/xeres/ui/support/util/UiUtils.java @@ -52,8 +52,7 @@ private UiUtils() throw new UnsupportedOperationException("Utility class"); } - private static final PseudoClass errorPseudoClass = PseudoClass.getPseudoClass("error"); - private static final PseudoClass warningPseudoClass = PseudoClass.getPseudoClass("warning"); + private static final PseudoClass dangerPseudoClass = PseudoClass.getPseudoClass("danger"); private static final String KEY_LISTENER = "listener"; private static final String KEY_POPUP = "popup"; @@ -80,7 +79,7 @@ public static void showAlertError(WebClientResponseException e) public static void showError(TextField field, String error) { - field.pseudoClassStateChanged(errorPseudoClass, true); + field.pseudoClassStateChanged(dangerPseudoClass, true); var label = new Label(); label.setText(error); @@ -111,22 +110,14 @@ public static void clearError(TextField field) { popup.hide(); } - field.pseudoClassStateChanged(errorPseudoClass, false); + field.pseudoClassStateChanged(dangerPseudoClass, false); } public static void showError(Node... nodes) { for (var node : nodes) { - node.pseudoClassStateChanged(errorPseudoClass, true); - } - } - - public static void showWarning(Node... nodes) - { - for (var node : nodes) - { - node.pseudoClassStateChanged(warningPseudoClass, true); + node.pseudoClassStateChanged(dangerPseudoClass, true); } } @@ -134,7 +125,7 @@ public static void clearError(Node... nodes) { for (var node : nodes) { - node.pseudoClassStateChanged(errorPseudoClass, false); + node.pseudoClassStateChanged(dangerPseudoClass, false); } } @@ -172,8 +163,7 @@ public static void setDefaultIcon(Stage stage) public static void setDefaultStyle(Scene scene) { - scene.getStylesheets().add("/view/javafx.css"); - //scene.getStylesheets().add("/view/javafx-dark.css"); + scene.getStylesheets().add("/view/default.css"); } /** @@ -263,6 +253,10 @@ public static Window getWindow(Event event) if (target instanceof MenuItem menuItem) { + if (menuItem.getParentMenu() != null) + { + return menuItem.getParentMenu().getParentPopup().getOwnerWindow(); + } return menuItem.getParentPopup().getOwnerWindow(); } else if (target instanceof Node node) diff --git a/ui/src/main/resources/image/avatar_16.png b/ui/src/main/resources/image/avatar_16.png deleted file mode 100644 index f41169ca78074fc812f0ff1314fc1c279443c2c5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 650 zcmV;50(Jd~P)Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!TPcUY)`Gw79d#2Or9!Vq;)nqdH zu~;nX)oS$uJs)Wtfj~g5RzLK5y_XJ$gOy69)Ov${zYm2%;cGM+eRVh-de|l+o@*TW ze7>~H<+?8t-8SDqsZ@f+V&M!1!^=vg@@c!>j*qDi6bi+u)oOh}kN*V9@Sexxd5<|S zV7J@PG#U*({U031Jw`8<7ciMjE=unNqtSSbM4s>hGMVgz(mO#c7K792e9jA?f>0(z zCX<15I!!pLJnVKmVyo3^xN96QP_NfxlnOAP&jHN<;k{lDlF1|#i$!R++vKPG9H1*$ zgTdgHR;z{CYz8Woih6_yg+dSr1PCve%fI*xR;$&wd_JGSywm9r+uwkNr_(9nsPzml zfRRWf-mTYblG=X1r&fT`XawN0aylSGe2l0@?*) zN7o}9&k26(^?Dc%hl^M&_F7CLC>Vy(%H?txkH>(=xz%dn(S=APg0D!UfC#ZH`*|=J zd>06~-R`GY0675{69wSq0l(i*Bd&p#Y8MWNKVW`aK!ld$43p(@Nd$1Zs8Ep0<@m0k kw*Jg!vu_)X#yJ4MZ=+($en%m0wEzGB07*qoM6N<$g6MA_p8x;= diff --git a/ui/src/main/resources/image/avatar_32.png b/ui/src/main/resources/image/avatar_32.png new file mode 100644 index 0000000000000000000000000000000000000000..23de491645d823d24c6176478579c6bd4590507d GIT binary patch literal 1382 zcmV-s1)2JZP)Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!2kdb!2!6DYwZ941p7%uK~z{rwO2_< zWJee-+g5Rn+U;)R65|FjN;IILki=k%hdJbs3_}PQ@DlJ4$U);RD2aiQ93&odPz-tq z3I;+zNs!S5+?i3)xZu8v`)M1tADH49^#J3&CN~E%gg)F z&(AL%Bg||z2VGoT9PjV%&mA8hPj75&jCXf;KOY+#`&Zjyi2qF>I5_yN^78U;qN1Wc zg<&6)$s{~HJmeriLXPwEa{(d;+uPec!^6WrG&eW@E*H4LtptjSir&r1$ti{5&w_%2 z0%`d2@>2WN6^al6uZWqMnfjWVny(=qUd>*IApyJHo?B8<@)u5TzRj>6NapP9Oe`)g zKC7sxc!H^!t6yHiQ+w(mw5+VGDIy}mrpd1aPft$)&)M?x^FPM>-xCuPbJ|Z5ZV6OY zRz8i7kALLs($_&iK)@R|o9(g1V(CL}I1<#PJ#WG{BqZb`WVj~2J{Z2x&``h3%*-Dl z242m&@jz*5sXaM4`OEtxpy1=x^?w3U z+SVly7#R2tv;LugK9Yn-B;WO{SlQ*Y_Xh#HXJKKXtS%PFI?g-U=G->gp1$t*zql@X&<>WsbeQJ<-w8A;!nY9dih^ zYpmPa+Fl$S99-+y?m#8Hf+r^@MSFX@I5|0ya|*ngcXoEheC#bED=SN6WMr@kB)R3~ z9V?ov*L2!?*&w-rU@rz+r^HzrV0rt#Tc`#x@UjpJZo`&&7Xs)?ffrhE{Cg^%x=VEPbP445V)jG-`7^zDDI9pg)XhJf|g&g?Ik_zqu z5+ea5wu^TN%$>O-AnNMs8o>O8CQ9g?-Uu|xyx^zV%><<=ZpcJ)*zA zUknWmiPhCrv9hw_AV6$xZjLlGH2i8v043(>yon@scXu7#>?Sy$o}MntEio}s#-AZz znc}Nj5~M!n)AaQ8PuQ|=NPuuo9=_9+tOh+2oKxjSgR!x(0^gM)EiFxig@rkE!5e?p z*VmJP{s)NyHkiQJv4#eyh+XX_2`b0ux2$`_R-lSx_S5}bPtOrZ$jgC&fp4V%7!pA8 zB(c*ugK|tv3}Yx2)1@j*B*mJiQFJ*mH8mwhM@Plr;GoO}<_}7HyQQUNSZ)Ab23QBx z)z!bn#l;o#okpmf8O;vI6O#ayGS-TSB10&bt0!|{c6RoUs;VkGMa~&{z>LzfDTP$R z*s_Hxh2i1hGUjwWiEy4zHE57S0b%KfasEScOu8k2+S+38kiT;vUoPRX oW8}lr+S=Ohv2^EZhk_9Q0a?~pXoyYa^Z)<=07*qoM6N<$f`^`-@Bjb+ literal 0 HcmV?d00001 diff --git a/ui/src/main/resources/view/about/about.fxml b/ui/src/main/resources/view/about/about.fxml index bc53a7432..2ed7d1467 100644 --- a/ui/src/main/resources/view/about/about.fxml +++ b/ui/src/main/resources/view/about/about.fxml @@ -25,11 +25,8 @@ - + - - - @@ -37,11 +34,7 @@ - + - - - - - - - + - - + + - - - - - + @@ -85,28 +68,20 @@ - - + + - - - - - + - - - - - + - - + + @@ -115,8 +90,8 @@ - - + + @@ -128,8 +103,8 @@ - - + + @@ -139,17 +114,19 @@ - - - + + + + + + + + + - - + + diff --git a/ui/src/main/resources/view/account/account_creation.fxml b/ui/src/main/resources/view/account/account_creation.fxml index e2bc11685..f37f895b6 100644 --- a/ui/src/main/resources/view/account/account_creation.fxml +++ b/ui/src/main/resources/view/account/account_creation.fxml @@ -21,21 +21,16 @@ - - - - + + - + + + + + diff --git a/ui/src/main/resources/view/custom/editorview.fxml b/ui/src/main/resources/view/custom/editorview.fxml index 80ceadc5b..580beba8c 100644 --- a/ui/src/main/resources/view/custom/editorview.fxml +++ b/ui/src/main/resources/view/custom/editorview.fxml @@ -19,18 +19,18 @@ ~ along with Xeres. If not, see . --> - - + + - + - + + + - + \ No newline at end of file diff --git a/ui/src/main/resources/view/default.css b/ui/src/main/resources/view/default.css new file mode 100644 index 000000000..531f6307c --- /dev/null +++ b/ui/src/main/resources/view/default.css @@ -0,0 +1,235 @@ +/* + * Copyright (c) 2019-2023 by David Gerber - https://zapek.com + * + * This file is part of Xeres. + * + * Xeres is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Xeres is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Xeres. If not, see . + */ + + +.base-spacing { + -fx-padding: 12px; +} + +/*noinspection CssInvalidPseudoSelector,CssUnusedSymbol*/ +.text-field:danger { + -fx-text-box-border: red; + -fx-focus-color: red; +} + +/*noinspection CssInvalidPseudoSelector,CssUnusedSymbol*/ +.text-area:danger { + -fx-background-color: red; + -fx-text-fill: red; +} + +/*noinspection CssInvalidPseudoSelector*/ +.label:danger { + -fx-text-fill: red; +} + +.chat-list-pane { + -fx-background-color: -color-border-default; + -fx-padding: 1px; +} + +.chat-list { + -fx-background-color: -color-bg-default; +} + +/*noinspection CssUnusedSymbol*/ +.chat-list .list-cell { + -fx-padding: 2px 8px 2px 8px; +} + +/*noinspection CssInvalidPseudoSelector,CssUnusedSymbol*/ +.chat-list .list-cell:passive > .label { + -fx-text-fill: -color-fg-subtle; +} + +/*noinspection CssInvalidPseudoSelector,CssUnusedSymbol*/ +.chat-list .list-cell:passive > Text { + -fx-fill: -color-fg-subtle; +} + +.chat-list .list-cell .time { + -fx-padding: 0px 0.25em 0px 0px; + -fx-text-fill: -color-fg-muted; +} + +.chat-list .list-cell .action { + -fx-padding: 0px 0.25em 0px 0px; +} + +/* this is needed to display emojis before sending them */ +.chat-send { + -fx-font-family: "Segoe UI Emoji", "Noto Color Emoji", "Noto Emoji", "Apple Color Emoji"; +} + +.chat-user-list { + -fx-border-width: 0 1 1 1; +} + +#messageHeader { + -fx-background-color: -color-bg-subtle; + -fx-border-color: -color-border-default; + -fx-padding: 8px; +} + +#messagePane { + -fx-border-color: -color-border-default; + -fx-padding: 8px; + -fx-border-width: 0 1 1 1; +} + +.menu-bar { + -fx-background-color: transparent; +} + +/* led */ +.led-control { + -color: black; +} + +.led-status-ok { + -color: #adff2f; +} + +.led-status-warning { + -color: #ffa500; +} + +.led-status-error { + -color: #ff3333; +} + +.led-control .frame { + -fx-background-color: linear-gradient(from 14% 14% to 84% 84%, + rgba(20, 20, 20, 0.64706) 0%, + rgba(20, 20, 20, 0.64706) 15%, + rgba(41, 41, 41, 0.64706) 26%, + rgba(200, 200, 200, 0.40631) 85%, + rgba(200, 200, 200, 0.3451) 100%); + -fx-background-radius: 1024px; +} + +/*noinspection CssInvalidFunction*/ +.led-control .main { + -fx-background-color: linear-gradient(from 15% 15% to 83% 83%, + derive(-color, -80%) 0%, + derive(-color, -87%) 49%, + derive(-color, -80%) 100%); + -fx-background-radius: 1024px; +} + +/*noinspection CssInvalidFunction,CssInvalidPseudoSelector*/ +.led-control:on .main { + -fx-background-color: linear-gradient(from 15% 15% to 83% 83%, + derive(-color, -23%) 0%, + derive(-color, -50%) 49%, + -color 100%); +} + +.led-control .highlight { + -fx-background-color: radial-gradient(center 15% 15%, radius 50%, white 0%, transparent 100%); + -fx-background-radius: 1024; +} + +.credit-card { + -fx-background-color: rgba(255, 255, 255, 0); + -fx-background-radius: 19px; + -fx-border-color: black; + -fx-border-radius: 19px; +} + +#addFriendButton .ikonli-font-icon { + -fx-icon-color: blue; + -fx-icon-size: 24px; +} + +#webHelpButton .ikonli-font-icon { + -fx-icon-color: green; + -fx-icon-size: 24px; +} + +.imageview-avatar { + -fx-background-color: -color-bg-subtle; + -fx-border-color: -color-border-default; + -fx-padding: 1px; +} + +.color-00 { + -fx-text-fill: #CC0000; +} + +.color-01 { + -fx-text-fill: #006CAD; +} + +.color-02 { + -fx-text-fill: #4D9900; +} + +.color-03 { + -fx-text-fill: #6600CC; +} + +.color-04 { + -fx-text-fill: #A67D00; +} + +.color-05 { + -fx-text-fill: #009927; +} + +.color-06 { + -fx-text-fill: #0030C0; +} + +.color-07 { + -fx-text-fill: #CC009A; +} + +.color-08 { + -fx-text-fill: #B94600; +} + +.color-09 { + -fx-text-fill: #869900; +} + +.color-10 { + -fx-text-fill: #149900; +} + +.color-11 { + -fx-text-fill: #009960; +} + +.color-12 { + -fx-text-fill: #006CAD; +} + +.color-13 { + -fx-text-fill: #0099CC; +} + +.color-14 { + -fx-text-fill: #B300CC; +} + +.color-15 { + -fx-text-fill: #CC004D; +} + diff --git a/ui/src/main/resources/view/forum/forum_create.fxml b/ui/src/main/resources/view/forum/forum_create.fxml index f45df5424..2497dc647 100644 --- a/ui/src/main/resources/view/forum/forum_create.fxml +++ b/ui/src/main/resources/view/forum/forum_create.fxml @@ -24,14 +24,14 @@ - + - - + +