package com.twasyl.slideshowfx.controls.slideshow; import com.twasyl.slideshowfx.controls.*; import com.twasyl.slideshowfx.global.configuration.GlobalConfiguration; import com.twasyl.slideshowfx.osgi.OSGiManager; import com.twasyl.slideshowfx.server.SlideshowFXServer; import com.twasyl.slideshowfx.server.beans.chat.ChatMessage; import com.twasyl.slideshowfx.server.beans.quiz.QuizResult; import com.twasyl.slideshowfx.server.bus.Actor; import com.twasyl.slideshowfx.server.bus.EventBus; import com.twasyl.slideshowfx.snippet.executor.CodeSnippet; import com.twasyl.slideshowfx.snippet.executor.ISnippetExecutor; import com.twasyl.slideshowfx.utils.PlatformHelper; import com.twasyl.slideshowfx.utils.ResourceHelper; import de.jensd.fx.glyphs.fontawesome.FontAwesomeIcon; import de.jensd.fx.glyphs.fontawesome.FontAwesomeIconView; import io.vertx.core.json.JsonArray; import io.vertx.core.json.JsonObject; import javafx.beans.property.ObjectProperty; import javafx.beans.property.SimpleObjectProperty; import javafx.collections.ListChangeListener; import javafx.collections.ObservableList; import javafx.geometry.Pos; import javafx.scene.layout.StackPane; import javafx.scene.shape.Circle; import java.util.Base64; import java.util.Optional; import java.util.logging.Logger; import static com.twasyl.slideshowfx.server.service.AbstractSlideshowFXService.*; import static com.twasyl.slideshowfx.server.service.ISlideshowFXServices.SERVICE_CHAT_ATTENDEE_HISTORY; import static com.twasyl.slideshowfx.server.service.PresenterChatService.SERVICE_CHAT_PRESENTER_ON_MESSAGE; import static com.twasyl.slideshowfx.server.service.QuizService.SERVICE_QUIZ_ON_RESULT; /** * A pane that displays a presentation. * * @author Thierry Wasylczenko * @version 1.1 * @since SlideshowFX 1.0 */ public class SlideshowPane extends StackPane implements Actor { private static final Logger LOGGER = Logger.getLogger(SlideshowPane.class.getName()); private final ObjectProperty<PresentationBrowser> browser = new SimpleObjectProperty<>(); private final ObjectProperty<Circle> pointer = new SimpleObjectProperty<>(); private final ChatPanel chatPanel = new ChatPanel(); private final QuizPanel quizPanel = new QuizPanel(); private final CollapsibleToolPane collapsibleToolPane = new CollapsibleToolPane(); /** * Creates a SlideshowPane object for the given {@code context}. The slideshow will be started at the * given {@link Context#getStartAtSlideId()}. * * @param context The context to create the {@link SlideshowPane}. * @see com.twasyl.slideshowfx.controls.slideshow.Context */ public SlideshowPane(Context context) { super(); EventBus.getInstance().subscribe(SERVICE_QUIZ_ON_RESULT, this) .subscribe(SERVICE_CHAT_PRESENTER_ON_MESSAGE, this); this.setAlignment(Pos.TOP_LEFT); this.getStylesheets().add(ResourceHelper.getExternalForm("/com/twasyl/slideshowfx/css/Default.css")); this.initializeBrowser() ; if(SlideshowFXServer.getSingleton() != null) { this.initializeChatPanel(); this.initializeCollapsibleToolPane(); } this.setCursor(null); } @Override public boolean supportsMessage(Object message) { return message != null && (message instanceof QuizResult || message instanceof JsonObject); } @Override public void onMessage(Object message) { if(message instanceof QuizResult) { this.publishQuizResult((QuizResult) message); } else if(message instanceof JsonObject) { final JsonObject jsonMessage = (JsonObject) message; if("chat-message".equals(jsonMessage.getString(JSON_KEY_BROADCAST_MESSAGE_TYPE))) { final JsonObject content = jsonMessage.getJsonObject(JSON_KEY_MESSAGE); if(content != null) { this.publishMessage(ChatMessage.build(content.encode(), null)); } } } } /** * Initialize the browser that displays the presentation. */ private final void initializeBrowser() { this.browser.set(new PresentationBrowser()); this.browser.get().setBackend(this); this.getChildren().add(this.browser.get()); } /** * Initialize the pane that contains all buttons related when the server is running (chat, QR code, quiz). */ private final void initializeCollapsibleToolPane() { final FontAwesomeIconView qrCodeIcon = new FontAwesomeIconView(FontAwesomeIcon.QRCODE); qrCodeIcon.setGlyphSize(32); qrCodeIcon.setGlyphStyle("-fx-fill: app-color-orange"); final FontAwesomeIconView chatIcon = new FontAwesomeIconView(FontAwesomeIcon.COMMENTS_ALT); chatIcon.setGlyphSize(32); chatIcon.setGlyphStyle("-fx-fill: app-color-orange"); final FontAwesomeIconView quizIcon = new FontAwesomeIconView(FontAwesomeIcon.QUESTION); quizIcon.setGlyphSize(32); quizIcon.setGlyphStyle("-fx-fill: app-color-orange"); this.collapsibleToolPane.addContent(qrCodeIcon, new QRCodePanel()) .addContent(chatIcon, this.chatPanel) .addContent(quizIcon, this.quizPanel); this.getChildren().add(this.collapsibleToolPane); } /** * This method is called by the presentation in order to execute a code snippet. The executor is identified by the * {@code snippetExecutorCode} and retrieved in the OSGi context to get the {@link com.twasyl.slideshowfx.snippet.executor.ISnippetExecutor} * instance that will execute the code. * The code to execute is passed to this method in Base64 using the {@code base64CodeSnippet} parameter. The execution * result will be pushed back to the presentation in the HTML element {@code consoleOutputId}. * * @param snippetExecutorCode The unique identifier of the executor that will execute the code. * @param base64CodeSnippet The code snippet to execute, given in Base64. * @param consoleOutputId The HTML element that will be updated with the execution result. */ public void executeCodeSnippet(final String snippetExecutorCode, final String base64CodeSnippet, final String consoleOutputId) { if(snippetExecutorCode != null) { final Optional<ISnippetExecutor> snippetExecutor = OSGiManager.getInstance().getInstalledServices(ISnippetExecutor.class) .stream() .filter(executor -> snippetExecutorCode.equals(executor.getCode())) .findFirst(); if(snippetExecutor.isPresent()) { final String decodedSnippet = new String(Base64.getDecoder().decode(base64CodeSnippet), GlobalConfiguration.getDefaultCharset()); final CodeSnippet codeSnippetDecoded = CodeSnippet.toObject(decodedSnippet); final ObservableList<String> consoleOutput = snippetExecutor.get().execute(codeSnippetDecoded); consoleOutput.addListener((ListChangeListener<String>) change -> { // Push the execution result to the presentation. PlatformHelper.run(() -> { while (change.next()) { if (change.wasAdded()) { change.getAddedSubList() .stream() .forEach(line -> this.browser.get().updateCodeSnippetConsole(consoleOutputId, line)); } } change.reset(); }); }); } } } /** * Retrieve the chat history and display it in the {@link #chatPanel}. */ private void initializeChatPanel() { final JsonObject request = new JsonObject() .put(JSON_KEY_SERVICE, SERVICE_CHAT_ATTENDEE_HISTORY) .put(JSON_KEY_DATA, new JsonObject()); final JsonArray history = SlideshowFXServer.getSingleton().callService(request.encode()) .getJsonArray(JSON_KEY_CONTENT); if(history != null) { for(Object message : history) { this.publishMessage(ChatMessage.build(((JsonObject) message).encode(), null)); } } } public ObjectProperty<PresentationBrowser> browserProperty() { return browser; } public PresentationBrowser getBrowser() { return this.browserProperty().get(); } public void setBrowser(PresentationBrowser browser) { this.browser.set(browser); } /** * This method publish the given <code>chatMessage</code> to the presenter. * @param chatMessage The message to publish. * @throws java.lang.NullPointerException If the message is null */ public void publishMessage(ChatMessage chatMessage) { if(chatMessage == null) throw new NullPointerException("The message to publish can not be null"); PlatformHelper.run(() -> this.chatPanel.addMessage(chatMessage)); } /** * This method publish the given {@link QuizResult} to the scene. * @param result The result to publish. * @throws java.lang.NullPointerException If the result is null */ public void publishQuizResult(QuizResult result) { if(result == null) throw new NullPointerException("The QuizResult to publish can not be null"); this.quizPanel.setQuizResult(result); } }