Skip to content

Commit

Permalink
Add handling for chat and forum URIs
Browse files Browse the repository at this point in the history
Chat shows the chat room and forum shows the forum group and possible message.
  • Loading branch information
zapek committed Aug 14, 2024
1 parent ffff8b0 commit d506714
Show file tree
Hide file tree
Showing 11 changed files with 188 additions and 33 deletions.
27 changes: 27 additions & 0 deletions ui/src/main/java/io/xeres/ui/OpenUriEvent.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/*
* Copyright (c) 2024 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 <http://www.gnu.org/licenses/>.
*/

package io.xeres.ui;

import io.xeres.common.events.SynchronousEvent;
import io.xeres.ui.support.uri.ContentParser;

public record OpenUriEvent(ContentParser contentParser) implements SynchronousEvent
{
}
33 changes: 28 additions & 5 deletions ui/src/main/java/io/xeres/ui/controller/MainWindowController.java
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
import io.xeres.common.rsid.Type;
import io.xeres.common.util.ByteUnitUtils;
import io.xeres.ui.JavaFxApplication;
import io.xeres.ui.OpenUriEvent;
import io.xeres.ui.client.ConfigClient;
import io.xeres.ui.client.IdentityClient;
import io.xeres.ui.client.LocationClient;
Expand All @@ -40,6 +41,8 @@
import io.xeres.ui.custom.led.LedControl;
import io.xeres.ui.custom.led.LedStatus;
import io.xeres.ui.support.tray.TrayService;
import io.xeres.ui.support.uri.ChatRoomContentParser;
import io.xeres.ui.support.uri.ForumContentParser;
import io.xeres.ui.support.uri.UriService;
import io.xeres.ui.support.util.TooltipUtils;
import io.xeres.ui.support.util.UiUtils;
Expand All @@ -48,10 +51,7 @@
import javafx.fxml.FXML;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.Menu;
import javafx.scene.control.MenuItem;
import javafx.scene.control.*;
import javafx.scene.input.Clipboard;
import javafx.scene.input.ClipboardContent;
import javafx.scene.layout.HBox;
Expand All @@ -63,6 +63,8 @@
import net.rgielen.fxweaver.core.FxmlView;
import org.kordamp.ikonli.fontawesome5.FontAwesomeSolid;
import org.kordamp.ikonli.javafx.FontIcon;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.event.ContextClosedEvent;
import org.springframework.context.event.EventListener;
import org.springframework.core.env.Environment;
Expand All @@ -88,10 +90,17 @@ public class MainWindowController implements WindowController
private static final String XERES_DOCS_URL = "https://xeres.io/docs";
private static final String XERES_BUGS_URL = "https://github.com/zapek/Xeres/issues/new/choose";
private static final String XERES_FORUMS_URL = "https://github.com/zapek/Xeres/discussions";
private static final Logger log = LoggerFactory.getLogger(MainWindowController.class);

private static final int CHAT_TAB_INDEX = 1;
private static final int FORUM_TAB_INDEX = 2;

@FXML
private StackPane stackPane;

@FXML
private TabPane tabPane;

@FXML
private Label titleLabel;

Expand Down Expand Up @@ -278,7 +287,7 @@ public void initialize()
fileChooser.setInitialDirectory(new File(AppDirsFactory.getInstance().getUserDownloadsDir(null, null, null)));
fileChooser.getExtensionFilters().add(new ExtensionFilter(bundle.getString("file-requester.images"), "*.xml"));
fileChooser.setInitialFileName("xeres_backup.xml");
var selectedFile = fileChooser.showSaveDialog(UiUtils.getWindow(event));
var selectedFile = fileChooser.showSaveDialog(getWindow(event));
if (selectedFile != null)
{
DataBufferUtils.write(configClient.getBackup(), selectedFile.toPath(), StandardOpenOption.CREATE, StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING).subscribe();
Expand Down Expand Up @@ -518,6 +527,20 @@ private void setDhtInfo(DhtInfo newDhtInfo)
}
}

@EventListener
public void handleOpenUriEvents(OpenUriEvent event)
{
switch (event.contentParser())
{
case ChatRoomContentParser chatRoomContentParser -> tabPane.getSelectionModel().select(CHAT_TAB_INDEX);
case ForumContentParser forumContentParser -> tabPane.getSelectionModel().select(FORUM_TAB_INDEX);
default ->
{
// Nothing to do
}
}
}

@EventListener
public void onApplicationEvent(ContextClosedEvent ignored)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@

import io.xeres.common.i18n.I18nUtils;
import io.xeres.common.message.chat.*;
import io.xeres.ui.OpenUriEvent;
import io.xeres.ui.client.ChatClient;
import io.xeres.ui.client.LocationClient;
import io.xeres.ui.client.ProfileClient;
Expand Down Expand Up @@ -54,6 +55,9 @@
import net.rgielen.fxweaver.core.FxmlView;
import org.kordamp.ikonli.fontawesome5.FontAwesomeSolid;
import org.kordamp.ikonli.javafx.FontIcon;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;

import java.io.IOException;
Expand All @@ -65,8 +69,10 @@
import java.util.ResourceBundle;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static io.xeres.common.message.chat.ChatConstants.TYPING_NOTIFICATION_DELAY;
import static javafx.scene.control.Alert.AlertType.WARNING;
import static org.apache.commons.lang3.ObjectUtils.isEmpty;
import static org.apache.commons.lang3.StringUtils.isNotBlank;

Expand All @@ -84,6 +90,7 @@ public class ChatViewController implements Controller
private static final String SUBSCRIBED_MENU_ID = "subscribed";
private static final String UNSUBSCRIBED_MENU_ID = "unsubscribed";
private static final String COPY_LINK_MENU_ID = "copyLink";
private static final Logger log = LoggerFactory.getLogger(ChatViewController.class);

@FXML
private TreeView<RoomHolder> roomTree;
Expand Down Expand Up @@ -251,6 +258,18 @@ public void initialize() throws IOException
createChatRoom.setOnAction(event -> windowManager.openChatRoomCreation());
}

@EventListener
public void handleOpenUriEvents(OpenUriEvent event)
{
if (event.contentParser() instanceof ChatRoomContentParser chatRoomContentParser)
{
var chatRoomId = chatRoomContentParser.getChatRoomId();

getAllTreeItem(chatRoomId).ifPresentOrElse(treeItem -> Platform.runLater(() -> roomTree.getSelectionModel().select(treeItem)),
() -> UiUtils.alert(WARNING, bundle.getString("chat.room.not-found")));
}
}

private void createRoomTreeContextMenu()
{
var subscribeItem = new MenuItem(I18nUtils.getString("chat.room.join"));
Expand Down Expand Up @@ -523,6 +542,13 @@ private Optional<TreeItem<RoomHolder>> getSubscribedTreeItem(long roomId)
.findFirst();
}

private Optional<TreeItem<RoomHolder>> getAllTreeItem(long roomId)
{
return Stream.concat(subscribedRooms.getChildren().stream(), Stream.concat(publicRooms.getChildren().stream(), privateRooms.getChildren().stream()))
.filter(roomHolderTreeItem -> roomHolderTreeItem.getValue().getRoomInfo().getId() == roomId)
.findFirst();
}

public void showMessage(ChatRoomMessage chatRoomMessage)
{
if (chatRoomMessage.isEmpty())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,13 @@
import com.fasterxml.jackson.databind.ObjectMapper;
import io.xeres.common.i18n.I18nUtils;
import io.xeres.common.id.Id;
import io.xeres.common.id.MessageId;
import io.xeres.common.message.forum.ForumGroup;
import io.xeres.common.message.forum.ForumMessage;
import io.xeres.common.rest.forum.PostRequest;
import io.xeres.common.rest.notification.forum.AddForumGroups;
import io.xeres.common.rest.notification.forum.AddForumMessages;
import io.xeres.ui.OpenUriEvent;
import io.xeres.ui.client.ForumClient;
import io.xeres.ui.client.NotificationClient;
import io.xeres.ui.controller.Controller;
Expand Down Expand Up @@ -66,6 +68,7 @@
import java.util.*;
import java.util.stream.Stream;

import static javafx.scene.control.Alert.AlertType.WARNING;
import static javafx.scene.control.TreeTableColumn.SortType.DESCENDING;

@Component
Expand Down Expand Up @@ -150,6 +153,8 @@ public class ForumViewController implements Controller
private final TreeItem<ForumGroup> popularForums;
private final TreeItem<ForumGroup> otherForums;

private MessageId messageIdToSelect;

public ForumViewController(ForumClient forumClient, ResourceBundle bundle, NotificationClient notificationClient, WindowManager windowManager, ObjectMapper objectMapper, MarkdownService markdownService, UriService uriService)
{
this.forumClient = forumClient;
Expand Down Expand Up @@ -218,6 +223,57 @@ public void initialize()
getForumGroups();
}

@EventListener
public void handleOpenUriEvent(OpenUriEvent event)
{
if (event.contentParser() instanceof ForumContentParser forumContentParser)
{
var group = forumContentParser.getId();
var message = forumContentParser.getMsgId();

Stream.concat(Stream.concat(Stream.concat(ownForums.getChildren().stream(), subscribedForums.getChildren().stream()), popularForums.getChildren().stream()), otherForums.getChildren().stream())
.filter(forumGroupTreeItem -> forumGroupTreeItem.getValue().getGxsId().equals(group))
.findFirst()
.ifPresentOrElse(forumGroupTreeItem -> {
setMessageToSelect(message);
Platform.runLater(() -> {
if (forumGroupTreeItem.equals(forumTree.getSelectionModel().getSelectedItem()))
{
// We need to select the message now if we're already on the right group
// because it won't be selected for us automatically.
selectMessageIfNeeded();
}
else
{
forumTree.getSelectionModel().select(forumGroupTreeItem);
}
});
}, () -> UiUtils.alert(WARNING, bundle.getString("forum.view.group.not-found")));
}
}

private void setMessageToSelect(MessageId message)
{
if (message != null)
{
messageIdToSelect = message;
}
}

private void selectMessageIfNeeded()
{
if (messageIdToSelect != null)
{
forumMessagesRoot.getChildren().stream()
.filter(forumMessageTreeItem -> forumMessageTreeItem.getValue().getMessageId().equals(messageIdToSelect))
.findFirst()
.ifPresentOrElse(forumMessageTreeItem -> Platform.runLater(() -> forumMessagesTreeTableView.getSelectionModel().select(forumMessageTreeItem)),
() -> UiUtils.alert(WARNING, bundle.getString("forum.view.message.not-found")));

messageIdToSelect = null;
}
}

private void createForumTreeContextMenu()
{
var subscribeItem = new MenuItem(I18nUtils.getString("forum.tree.subscribe"));
Expand Down Expand Up @@ -427,6 +483,7 @@ private void changeSelectedForumGroup(ForumGroup forumGroup)
forumMessagesTreeTableView.sort();
clearMessage();
newThread.setDisable(false);
selectMessageIfNeeded();
}))
.doOnError(UiUtils::showAlertError) // XXX: cleanup on error?
.doFinally(signalType -> forumMessagesState(false))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,20 +60,20 @@ public String getAuthority()
public Content parse(UriComponents uriComponents, String text, UriAction uriAction)
{
var nameParameter = uriComponents.getQueryParams().getFirst(PARAMETER_NAME);
var id = uriComponents.getQueryParams().getFirst(PARAMETER_ID);
var idParameter = uriComponents.getQueryParams().getFirst(PARAMETER_ID);

if (Stream.of(nameParameter, id).anyMatch(StringUtils::isBlank))
if (Stream.of(nameParameter, idParameter).anyMatch(StringUtils::isBlank))
{
return ContentText.EMPTY;
}

name = nameParameter;
if (id.length() > 1 && id.startsWith(CHAT_ROOM_PREFIX))
if (idParameter.length() > 1 && idParameter.startsWith(CHAT_ROOM_PREFIX))
{
chatRoomId = ContentParser.getLongArgument(id.substring(1));
chatRoomId = ContentParser.getLongArgument(idParameter.substring(1));
}

return new ContentUri(id, nameParameter, uri -> uriAction.openUri(this));
return new ContentUri(idParameter, nameParameter, uri -> uriAction.openUri(this));
}

public static String generate(String name, long chatRoomId)
Expand Down
4 changes: 3 additions & 1 deletion ui/src/main/java/io/xeres/ui/support/uri/ContentParser.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@
import org.springframework.web.util.UriComponents;
import org.springframework.web.util.UriUtils;

import java.util.Locale;

import static java.nio.charset.StandardCharsets.UTF_8;

public interface ContentParser
Expand Down Expand Up @@ -75,7 +77,7 @@ static long getLongArgument(String s)
{
try
{
return Long.parseUnsignedLong(s);
return Long.parseUnsignedLong(s.toLowerCase(Locale.ROOT), 16);
}
catch (NumberFormatException e)
{
Expand Down
25 changes: 6 additions & 19 deletions ui/src/main/java/io/xeres/ui/support/uri/UriService.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,26 +19,22 @@

package io.xeres.ui.support.uri;

import io.xeres.common.rest.file.AddDownloadRequest;
import io.xeres.ui.OpenUriEvent;
import io.xeres.ui.support.markdown.UriAction;
import io.xeres.ui.support.util.UiUtils;
import io.xeres.ui.support.window.WindowManager;
import javafx.application.HostServices;
import org.springframework.context.annotation.Lazy;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Service;

import static javafx.scene.control.Alert.AlertType.WARNING;

@Service
public class UriService implements UriAction
{
private final WindowManager windowManager;
private final ApplicationEventPublisher eventPublisher;

private static HostServices hostServices;

public UriService(@Lazy WindowManager windowManager)
public UriService(ApplicationEventPublisher eventPublisher)
{
this.windowManager = windowManager;
this.eventPublisher = eventPublisher;
}

public void setHostServices(HostServices hostServices)
Expand All @@ -49,16 +45,7 @@ public void setHostServices(HostServices hostServices)
@Override
public void openUri(ContentParser contentParser)
{
switch (contentParser)
{
case CertificateContentParser certificateContentParser -> windowManager.openAddPeer(certificateContentParser.getRadix());
case FileContentParser fileContentParser -> windowManager.openAddDownload(
new AddDownloadRequest(fileContentParser.getName(),
fileContentParser.getSize(),
fileContentParser.getHash(),
null));
default -> UiUtils.alert(WARNING, "The link '" + contentParser.getAuthority() + "' is not supported yet.");
}
eventPublisher.publishEvent(new OpenUriEvent(contentParser));
}

/**
Expand Down
Loading

0 comments on commit d506714

Please sign in to comment.