Skip to content

Commit

Permalink
Merge pull request #279 from human-nature-lab/frontend-modules
Browse files Browse the repository at this point in the history
Frontend modules
  • Loading branch information
wyattis committed Feb 3, 2023
2 parents 8ac2fd4 + 76220d0 commit 11ad9b3
Show file tree
Hide file tree
Showing 319 changed files with 45,603 additions and 5,643 deletions.
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ docs
logo
logs
db
dev
project/project
project/target
conf/generated.keystore
Expand All @@ -30,6 +31,8 @@ experiments
dev.bat
dev.sh
node_modules
client-graph.js
client-html.html
csv

.vscode
generated
33 changes: 33 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Contributing
There are many different ways to contribute to the Breadboard project. We welcome issues, code, documentation
and examples.

## Development

### Environment setup
Breadboard works with Java 7+. Make sure the Java SDK is installed before beginning development.

- `cd frontend`
- `npm install`

### Running
#### Backend development
If modifications will be made to files in the **frontend** directory, use the frontend development instructions instead
- Start the play framework server using `sbt "run -Dconfig.file=conf/application-prod.conf"`

#### Frontend development
This uses a slightly different configuration to allow hot module replacement via webpack on frontend files.
- Start the webpack server using `cd frontend && npm start`
- Start the play framework server using `sbt "run -Dconfig.file=conf/application-dev.conf"`


From a terminal run . This will start a dev server which will
automatically rebuild the frontend files whenever a file changes.


## Production

### Compile jars
- `cd frontend && npm run build` to build frontend assets if this code has changed
- `sh create_prod_dist.sh` to compile distributable files
- In many cases, only copying the compiled **breadboard.jar** file is enough to update existing Breadboard applications.
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,6 @@ breadboard is built using:
* [CodeMirror](https://codemirror.net/)

Also [Apache Commons](https://commons.apache.org/), [imgscalr](https://github.com/thebuzzmedia/imgscalr), [JUNG](http://jung.sourceforge.net/), [jQuery](https://jquery.com/), [Modernizr](https://modernizr.com/), [Underscore](http://underscorejs.org/), and [Bootstrap](http://getbootstrap.com/).

### Contributing
See the [contributing guide](CONTRIBUTING.md)
37 changes: 19 additions & 18 deletions app/Global.java
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,15 @@ public void onStart(Application app) {
SqlRow versionTableCount = Ebean.createSqlQuery(sql).findUnique();
String count = versionTableCount.getString("table_count");

// If the breadboard_version table doesn't exist we're < v2.3
if (count.equals("0")) {

// Create the breadboard_version table
sql = "create table breadboard_version ( version varchar(255) ); ";
Ebean.createSqlUpdate(sql).execute();
// Update the version
sql = "insert into breadboard_version values ('v2.3.1'); ";
String version = play.Play.application().configuration().getString("application.version");
sql = "insert into breadboard_version values ('" + version + "'); ";
Ebean.createSqlUpdate(sql).execute();

// Create the languages table
Expand Down Expand Up @@ -155,25 +157,24 @@ public void onStart(Application app) {
// TODO: Add message telling user what was done
// TODO: Load v2.3 version notes as message from file system

// Upgrade to version 2.4.0
version2Point4Upgrade();
} else {
// The breadboard_version table exists, let's check if we're v2.3 or v2.4
sql = "select version from breadboard_version limit 1;";
SqlRow versionString = Ebean.createSqlQuery(sql).findUnique();
String version = versionString != null ? versionString.getString("version") : "";
if (version.equals("v2.3.0") || version.equals("v2.3.1")) {
// Add v2.4.0 fields
version2Point4Upgrade();
} // Otherwise, no upgrade needed
}
}

//InitialData.insert(app);
boolean isWin = System.getProperty("os.name").toUpperCase().indexOf("WIN") >= 0;
String cwd = System.getProperty("user.dir");
// TODO: If omitted, this should default to PROD
String mode = play.Play.application().configuration().getString("application.mode");
if(mode.toUpperCase().equals("DEV")) {
// Try to start the assets server
try {
ProcessBuilder pb = new ProcessBuilder("node", cwd + "/frontend/webpack/webpack.server.js");
pb.directory(new File(cwd + "/frontend"));
pb.inheritIO();
process = pb.start();
} catch (Exception e) {
e.printStackTrace();
}
}
// TODO: We could build the production resources when the framework starts instead of having to build them manually
private void version2Point4Upgrade() {
// Changes to support Experiment fileMode
String sql = "alter table experiments add column if not exists file_mode bit default 0;";
Ebean.createSqlUpdate(sql).execute();
}

@Override
Expand Down
106 changes: 106 additions & 0 deletions app/actors/FileWatcherActor.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
package actors;

import actors.FileWatcherActorProtocol.FileWatch;
import akka.actor.UntypedActor;
import models.Admin;
import models.FileWatcher;
import org.apache.commons.io.FileUtils;
import play.Logger;

import java.io.IOException;
import java.nio.file.*;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;

import static java.nio.file.StandardWatchEventKinds.ENTRY_CREATE;
import static java.nio.file.StandardWatchEventKinds.ENTRY_DELETE;
import static java.nio.file.StandardWatchEventKinds.ENTRY_MODIFY;

public class FileWatcherActor extends UntypedActor {
private static DateFormat dateFormat;

public FileWatcherActor() {
dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
}


@Override
public void onReceive(Object message) {
if (message instanceof FileWatch) {
FileWatcher fileWatcher = ((FileWatch) message).fileWatcher;

// If fileMode for the selected experiment is false, do nothing
/* TODO: if there are multiple admins listening, they could each be watching different experiments which is weird in the context of a single file watcher */
// if ( fileWatcher.getAdminListeners().isEmpty() || fileWatcher.getAdminListeners().get(0).getUser().selectedExperiment == null || (! fileWatcher.getAdminListeners().get(0).getUser().selectedExperiment.getFileMode()) ) {
// return;
//}
fileWatcher.incrementUpdateIteration();
Long updateIteration = fileWatcher.getUpdateIteration();
if (updateIteration % 100 == 0) {
Logger.debug(dateFormat.format(new Date()) + " - FileWatch:" + updateIteration);
}

WatchKey watchKey;
boolean changed = false;
watchKey = fileWatcher.getWatcher().poll();
if (watchKey == null) {
return;
}

Path dir = fileWatcher.getWatchKeys().get(watchKey);
if (dir == null) {
Logger.error("Event triggered from watchKey not registered properly.");
return;
}

for (WatchEvent<?> event: watchKey.pollEvents()) {
WatchEvent<Path> ev = (WatchEvent<Path>) event;
Path name = ev.context();
Path child = dir.resolve(name);
Logger.debug(child.toString());
String selectedExperimentDirectory = (fileWatcher.getAdminListeners().get(0).getUser().selectedExperiment == null) ? "" : fileWatcher.getAdminListeners().get(0).getUser().selectedExperiment.getDirectoryName();
boolean selectedExperimentChanged = child.toString().contains(selectedExperimentDirectory);

if (ev.kind() == ENTRY_CREATE && child.toFile().isDirectory()) {
// new directory, need to watch it
try {
fileWatcher.registerRecursive(child);
} catch (IOException ioe) {
Logger.error("Unable to watch directory " + child.toFile().getPath() + " check the permissions.");
}
Logger.debug("DIRECTORY ENTRY_CREATE");
} else if ((ev.kind() == ENTRY_CREATE || ev.kind() == ENTRY_MODIFY) && child.toFile().isFile() && selectedExperimentChanged) {
// A file was modified or created in the selected experiment directory, update the admin
changed = true;
if (child.toFile().getParent().endsWith("/Steps")) {
// Step was modified or created, send to ScriptEngine
try {
String stepContents = FileUtils.readFileToString(child.toFile());
fileWatcher.loadStep(stepContents, fileWatcher.getAdminListeners().get(0).getOut(), child.getFileName().toString());
} catch (IOException ioe) {
Logger.error("Unable to read contents of Step " + child.getFileName().toString() + " check your permissions.");
}
}
Logger.debug("FILE ENTRY_MODIFY || ENTRY_CREATE");
} else if (ev.kind() == ENTRY_DELETE) {
Logger.debug("ENTRY_DELETE");
}
}

if (changed) {
// Something was changed, let's update all admin listeners
for(Admin a : fileWatcher.getAdminListeners()) {
a.update();
}
}

boolean valid = watchKey.reset();
if (!valid) {
fileWatcher.getWatchKeys().remove(watchKey);
Logger.debug("Watched directory was deleted.");
}
}
}
}

13 changes: 13 additions & 0 deletions app/actors/FileWatcherActorProtocol.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package actors;

import models.FileWatcher;

public class FileWatcherActorProtocol {
public static class FileWatch {
final FileWatcher fileWatcher;
public FileWatch(FileWatcher fileWatcher) {
this.fileWatcher = fileWatcher;
}
}
}

85 changes: 5 additions & 80 deletions app/controllers/Application.java
Original file line number Diff line number Diff line change
@@ -1,16 +1,13 @@
package controllers;

import models.*;
import org.apache.commons.io.FileUtils;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.mindrot.jbcrypt.BCrypt;
import play.Logger;
import play.data.Form;
import play.libs.Json;
import play.mvc.*;
import play.mvc.Http.MultipartFormData;
import play.mvc.Http.MultipartFormData.FilePart;
import views.html.*;
import java.io.*;
import java.text.SimpleDateFormat;
Expand Down Expand Up @@ -84,7 +81,7 @@ public static Result authenticate() {
User user = User.findByEmail(email);

if (user != null) {
Logger.info("authenticate: uid = " + uid);
Logger.debug("authenticate: uid = " + uid);
user.uid = uid;
user.update();
result = Json.newObject();
Expand All @@ -103,9 +100,10 @@ public static Result logout() {
return unauthorized();
}

public static Result index() {
final File file = play.Play.application().getFile("assets/templates/breadboard.html");
return ok(file, true);
public static Result index () {
// final File file = play.Play.application().getFile("assets/templates/breadboard.html");
String assetsRoot = play.Play.application().configuration().getString("breadboard.assetsRoot", "/assets");
return ok(main.render(assetsRoot));
}

@Security.Authenticated(Secured.class)
Expand All @@ -115,82 +113,9 @@ public static Result getState() {
result.put("juid", session("juid"));
result.put("email", session("email"));
result.put("connectSocket", play.Play.application().configuration().getString("breadboard.wsUrl"));
result.put("imageUploadRoute", routes.Application.uploadImage().toString());
return ok(result);
}

@Security.Authenticated(Secured.class)
public static Result uploadImage() {
MultipartFormData body = request().body().asMultipartFormData();
FilePart picture = body.getFile("picture");
if (picture != null) {
String fileName = picture.getFilename();
String contentType = picture.getContentType();
File file = picture.getFile();

Map<String, String[]> values = body.asFormUrlEncoded();

try {
String experimentId = values.get("experimentId")[0];
Long eid = Long.parseLong(experimentId);
Experiment experiment = Experiment.findById(eid);
Image image = new Image();
image.fileName = fileName;
image.file = FileUtils.readFileToByteArray(file);
image.contentType = contentType;
experiment.images.add(image);
experiment.save();
} catch (NullPointerException npe) {
Logger.error("IOException in uploadImage(): " + npe.getMessage());
return ok("Error uploading");
} catch (NumberFormatException nfe) {
Logger.error("IOException in uploadImage(): " + nfe.getMessage());
return ok("Error uploading");
} catch (IOException ioe) {
Logger.error("IOException in uploadImage(): " + ioe.getMessage());
return ok("Error uploading");
}


//TODO: For web deployment consider storing images to filesystem
//Integer nextId = (Integer)Ebean.nextId(Image.class);
//String uniqueFileName = nextId.toString() + "_" + fileName;
//Logger.info("uniquefileName = " + uniqueFileName);
//String uploadPath = "";


return ok("File uploaded");
} else {
return ok("Error uploading");
}
}

public static Result getImage(Long imageId) {
Image image = Image.findById(imageId);

if (image != null) {
if (image.contentType != null && image.file != null) {
response().setContentType(image.contentType);
return ok(image.file);
}
}

return notFound();
}

public static Result getImageThumb(Long imageId) {
Image image = Image.findById(imageId);

if (image != null) {
if (image.contentType != null && image.thumbFile != null && image.thumbFile.length > 0) {
response().setContentType(image.contentType);
return ok(image.thumbFile);
}
}

return notFound();
}

@Security.Authenticated(Secured.class)
public static Result saveUserSettings() {
Form<UserSettings> userSettingsForm = Form.form(UserSettings.class);
Expand Down
Loading

0 comments on commit 11ad9b3

Please sign in to comment.