Skip to content

Commit

Permalink
[core] improve landing page experience (#25)
Browse files Browse the repository at this point in the history
  • Loading branch information
cyrilou242 authored Dec 2, 2023
1 parent b00ae42 commit c8cccb5
Show file tree
Hide file tree
Showing 8 changed files with 144 additions and 45 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -45,3 +45,6 @@ notebook-profile-*
### pom release artefacts
release.properties
pom.xml.releaseBackup

# template classes generated by jte
jte-classes/
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,10 @@
package tech.catheu.jnotebook;

import io.methvin.watcher.DirectoryChangeEvent;
import io.methvin.watcher.hashing.FileHash;
import io.reactivex.rxjava3.core.Observable;
import io.reactivex.rxjava3.subjects.PublishSubject;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import tech.catheu.jnotebook.evaluate.GreedyInterpreter;
Expand All @@ -32,36 +35,38 @@ public class InteractiveNotebook {

private static final Logger LOG = LoggerFactory.getLogger(InteractiveNotebook.class);

private static final String RESOURCES_HELLO_WORLD_NOTEBOOK = "/jnb_interactive/hello_world.jsh";
private static final String RESOURCES_HELLO_WORLD_NOTEBOOK =
"/jnb_interactive/hello_world.jsh";
private static final String FILESYSTEM_HELLO_WORLD_NAME = "hello_world.jsh";

private final Main.InteractiveConfiguration configuration;
private static final String JSHELL_SUFFIX = ".jsh";
private final StaticParser staticParser;
private final Interpreter interpreter;
private final Renderer renderer;
private final InteractiveServer server;
private InteractiveServer server;

public InteractiveNotebook(final Main.InteractiveConfiguration configuration) {
this.configuration = configuration;
final ShellProvider shellProvider = new ShellProvider(configuration);
this.staticParser = new StaticParser(shellProvider);
this.interpreter = new GreedyInterpreter(shellProvider);
this.renderer = new Renderer(configuration);
this.server = new InteractiveServer(configuration);
}

public void run() throws IOException {
prepare();
server.start();
final Observable<DirectoryChangeEvent> notebookEvents =
final Observable<DirectoryChangeEvent> fileChangeEvents =
PathObservables.of(Paths.get(configuration.notebookPath))
.filter(e -> e.path().toString().endsWith(JSHELL_SUFFIX));
//.subscribe(s -> server.sendReload()); to subscribe on a side scheduler

final PublishSubject<DirectoryChangeEvent> manualTriggers = PublishSubject.create();
this.server = new InteractiveServer(configuration,
path -> manualTriggers.onNext(
directoryChangeEvent(path)));
server.start();
LOG.info("Notebook server started. Go to http://localhost:" + configuration.port);

notebookEvents
manualTriggers
.mergeWith(fileChangeEvents)
.doOnEach(e -> server.sendStatus(NotebookServerStatus.COMPUTE))
.map(staticParser::staticSnippets)
.doOnError(InteractiveNotebook::logError)
Expand All @@ -72,6 +77,17 @@ public void run() throws IOException {
.subscribe(server::sendUpdate, InteractiveNotebook::logError);
}

@NonNull
private static DirectoryChangeEvent directoryChangeEvent(final Path path) {
return new DirectoryChangeEvent(
DirectoryChangeEvent.EventType.MODIFY,
false,
path,
FileHash.fromLong(0),
0,
path.getRoot());
}

private void prepare() {
// ensure notebook folder exists, if not, create it.
final Path notebooksFolder = Paths.get(configuration.notebookPath);
Expand All @@ -91,7 +107,9 @@ private static void logError(Throwable e) {


public void stop() throws IOException {
server.stop();
if (server != null) {
server.stop();
}
staticParser.stop();
interpreter.stop();
renderer.stop();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,13 +75,15 @@ public void render(final Main.RenderConfiguration config) {
final Interpreted interpreted = interpreter.interpret(staticParsing);
final Rendering render = renderer.render(interpreted);
final HtmlTemplateEngine templateEngine = new HtmlTemplateEngine();
String html = templateEngine.render(config, false, render.html());
final HtmlTemplateEngine.TemplateData model =
new HtmlTemplateEngine.TemplateData(config, false, render.html(), null);
String html = templateEngine.render(model);
if (!config.noOptimize) {
html = optimizeHtml(html);
}

final String outputPath = optional(config.outputPath)
.orElse(Files.getNameWithoutExtension(config.inputPath) + ".html");
final String outputPath =
optional(config.outputPath).orElse(Files.getNameWithoutExtension(config.inputPath) + ".html");
final File outputFile = FileUtils.getFile(outputPath);
FileUtils.write(outputFile, html, StandardCharsets.UTF_8);
LOG.info("Notebook rendered successfully and written to {}", outputFile);
Expand Down Expand Up @@ -139,7 +141,8 @@ public void handle(HttpExchange exchange) throws IOException {
responseStream.close();
}
});
final HtmlFileServer result = new HtmlFileServer("http://localhost:" + port, server);
final HtmlFileServer result =
new HtmlFileServer("http://localhost:" + port, server);
return result;
} catch (Exception e) {
LOG.error("Failed to create a server: ", e);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import tech.catheu.jnotebook.utils.JavaUtils;

import java.nio.file.Path;
import java.util.List;

public class HtmlTemplateEngine {

Expand All @@ -36,15 +37,13 @@ public HtmlTemplateEngine() {
}

// render is the generated notebook html
public String render(final Main.SharedConfiguration config, final boolean interactive,
final String render) {
final TemplateModel model = new TemplateModel(config, interactive, render);
public String render(TemplateData model) {
final TemplateOutput output = new StringOutput();
delegate.render("index.jte", model, output);
return output.toString();
}

public record TemplateModel(Main.SharedConfiguration config, boolean interactive, String render) {}
public record TemplateData(Main.SharedConfiguration config, boolean interactive, String render, List<Path> notebooksInPath) {}


}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
import io.undertow.util.Headers;
import io.undertow.websockets.WebSocketConnectionCallback;
import io.undertow.websockets.WebSocketProtocolHandshakeHandler;
import io.undertow.websockets.core.AbstractReceiveListener;
import io.undertow.websockets.core.BufferedTextMessage;
import io.undertow.websockets.core.WebSocketChannel;
import io.undertow.websockets.core.WebSockets;
import io.undertow.websockets.spi.WebSocketHttpExchange;
Expand All @@ -28,22 +30,30 @@
import tech.catheu.jnotebook.render.Rendering;

import java.io.IOException;
import java.util.ArrayList;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.function.Consumer;
import java.util.stream.Stream;

public class InteractiveServer {

private static final Logger LOG = LoggerFactory.getLogger(Main.class);
private static final Logger LOG = LoggerFactory.getLogger(InteractiveServer.class);

private final Main.InteractiveConfiguration configuration;
private final Consumer<Path> renderTrigger;

private Undertow server;
private final List<WebSocketChannel> channels = new ArrayList<>();
private final Queue<WebSocketChannel> channels = new ConcurrentLinkedQueue<>();
XnioWorker worker;
private Rendering lastUpdate;

public InteractiveServer(final Main.InteractiveConfiguration configuration) {
public InteractiveServer(final Main.InteractiveConfiguration configuration,
Consumer<Path> renderTrigger) {
this.configuration = configuration;
this.renderTrigger = renderTrigger;
}


Expand Down Expand Up @@ -94,7 +104,20 @@ private static class TemplatedHttpHandler implements HttpHandler {

@Override
public void handleRequest(HttpServerExchange exchange) throws Exception {
final String html = templateEngine.render(this.configuration, true, null);
final List<Path> notebooksInPath;
try (Stream<Path> files = Files.find(Path.of(configuration.notebookPath),
10,
(path, attributes) -> attributes.isRegularFile() && path.toString()
.endsWith(
".jsh"))) {
notebooksInPath = files.toList();
}
final HtmlTemplateEngine.TemplateData model = new HtmlTemplateEngine.TemplateData(
this.configuration,
true,
null,
notebooksInPath);
final String html = templateEngine.render(model);
exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, "text/html");
exchange.getResponseSender().send(html);
}
Expand All @@ -119,7 +142,8 @@ private void sendMessage(final String message) {
}
}
if (!messageSent) {
LOG.error("ERROR: trying to send updates but no client is opened. Go to http://localhost:" + configuration.port);
LOG.error(
"ERROR: trying to send updates but no client is opened. Go to http://localhost:" + configuration.port);
}
}

Expand All @@ -144,10 +168,29 @@ public void onConnect(WebSocketHttpExchange webSocketHttpExchange,
channels.add(channel);
// resend to every channel - not very correct but simpler for the moment
sendStatus(NotebookServerStatus.CONNECTED);
setupReceiver(channel);
if (lastUpdate != null) {
// resend to every channel - not necessary but simpler for the moment
sendUpdate(lastUpdate);
}
}

private void setupReceiver(WebSocketChannel channel) {
channel.getReceiveSetter().set(new AbstractReceiveListener() {
@Override
protected void onFullTextMessage(WebSocketChannel channel,
BufferedTextMessage message) throws IOException {
final String messageText = message.getData();
if (messageText.startsWith("refresh_")) {
final String substring = messageText.substring(8);
LOG.info("Triggering refresh for file {}", substring);
renderTrigger.accept(Path.of(substring));
} else {
LOG.error("Received unsupported message from websocket: {}", messageText);
}
}
});
channel.resumeReceives();
}
}
}
Empty file.
41 changes: 20 additions & 21 deletions jnotebook-core/src/main/jte/index.jte
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
@import tech.catheu.jnotebook.Main.InteractiveConfiguration
@import tech.catheu.jnotebook.server.HtmlTemplateEngine.TemplateModel
@import tech.catheu.jnotebook.server.HtmlTemplateEngine.TemplateData
@import java.nio.file.Path
@import java.util.Optional
@param TemplateModel model
@param TemplateData model
<!--/*
Copyright 2023 Cyril de Catheu
Expand Down Expand Up @@ -646,7 +647,9 @@
background-color: #ffe4e4;
}
[x-cloak] { display: none !important; }
[x-cloak] {
display: none !important;
}
</style>

<script defer class="jnb-no-opti" src="https://cdn.jsdelivr.net/npm/[email protected]/dist/cdn.min.js"></script>
Expand All @@ -670,7 +673,7 @@
</head>
<body>
<div x-cloak x-data="{ tocOpen: window.innerWidth >= 768, notebookStatus: 'Connected'}" class="flex">
<button x-on:click="tocOpen = ! tocOpen"
<button x-on:click="tocOpen=!tocOpen"
class="toc-toggle z-20 fixed right-2 top-2 md:right-auto md:left-3 md:top-[7px] text-slate-400 font-sans text-xs hover:underline cursor-pointer flex items-center bg-white py-1 px-3 md:p-0 rounded-full md:rounded-none border md:border-0 border-slate-200 shadow md:shadow-none">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" width="20"
height="20">
Expand All @@ -687,7 +690,7 @@
<div class="px-3 mb-1 mt-1 md:mt-0 text-xs md:text-[12px] uppercase tracking-wider text-slate-500 font-medium px-3 mb-1 leading-none">
ToC
</div>
<div x-on:click="tocOpen = ! tocOpen"
<div x-on:click="tocOpen=!tocOpen"
class="toc-toggle text-slate-500 absolute right-2 top-[11px] cursor-pointer z-10">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor"
stroke-width="2" class="w-4 w-4">
Expand All @@ -697,23 +700,13 @@
<div id="toc"></div>
</aside>
<div x-bind:class="tocOpen ? 'toc-offset' : ''" class="flex-auto h-screen overflow-y-auto scroll-container">
@if(model.render() != null)
<div id="notebook" class="flex flex-col items-center viewer-notebook flex-auto">
<div id="notebook" class="flex flex-col items-center viewer-notebook flex-auto">
@if(model.render() != null)
$unsafe{model.render()}
</div>
@else
<div id="notebook" class="flex flex-col items-center viewer-notebook flex-auto">
<div id="helper" class="viewer viewer-result w-full max-w-prose px-8">
<div class="relative">
<div class="overflow-y-hidden">
<p>Welcome to <code>jnotebook</code>. Learn more on <a href="https://jnotebook.catheu.tech">jnotebook.catheu.tech</a>.
</p>
<p>Edit a <code>.jsh</code> file in your <code>${((InteractiveConfiguration) model.config()).notebookPath}</code> folder to
make your notebook appear.</p></div>
</div>
</div>
</div>
@endif
@else
@template.landing(model=model)
@endif
</div>
</div>
@if(model.interactive())
<script defer>
Expand Down Expand Up @@ -761,6 +754,12 @@
update(socketEvent.data);
}
};
// something like this for the message passing
const textAreas = document.querySelectorAll("button.notebook-in-path")
.forEach(e => e.addEventListener('click', ev => {
socket.send("refresh_" + e.textContent)
}));
}
const port = "${Optional.ofNullable(((InteractiveConfiguration) model.config()).port).orElse(5002)}";
Expand Down
34 changes: 34 additions & 0 deletions jnotebook-core/src/main/jte/landing.jte
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
@import tech.catheu.jnotebook.Main.InteractiveConfiguration
@param tech.catheu.jnotebook.server.HtmlTemplateEngine.TemplateData model

<div id="helper" class="viewer viewer-result w-full max-w-prose px-8">
<div class="relative">
<div class="overflow-y-hidden">
<p>Welcome to <code>jnotebook</code>. Learn more on <a href="https://jnotebook.catheu.tech">jnotebook.catheu.tech</a>.
</p>
<p>Edit a <code>.jsh</code> file in your
<code>${((InteractiveConfiguration) model.config()).notebookPath}</code> folder to
launch your notebook, or click on a notebook below.</p>
<div>
<table>
<tr>
<td>Notebooks</td>
</tr>
@if(model.notebooksInPath() == null || model.notebooksInPath().isEmpty())
<tr>
<td>
<i>There are no notebooks in the <code>${((InteractiveConfiguration) model.config()).notebookPath}</code> folder.</i>
</td>
</tr>
@else
@for(final java.nio.file.Path p: model.notebooksInPath())
<tr>
<td><button class="notebook-in-path"><a>${p.toString()}</a></button></td>
</tr>
@endfor
@endif
</table>
</div>
</div>
</div>
</div>

0 comments on commit c8cccb5

Please sign in to comment.