forked from j62/ctbrec
1
0
Fork 0

Improve UI features for time limited recordings

This commit is contained in:
0xb00bface 2020-12-21 18:53:34 +01:00
parent 8fe48f91b7
commit 97715aecc5
6 changed files with 133 additions and 26 deletions

View File

@ -22,13 +22,15 @@ import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
/**
* Just a wrapper for Model, which augments it with JavaFX value binding properties, so that UI widgets get updated proeprly
*/
public class JavaFxModel implements Model {
private transient BooleanProperty onlineProperty = new SimpleBooleanProperty();
private transient BooleanProperty recordingProperty = new SimpleBooleanProperty();
private transient StringProperty onlineProperty = new SimpleStringProperty();
private transient StringProperty recordingProperty = new SimpleStringProperty();
private transient BooleanProperty pausedProperty = new SimpleBooleanProperty();
private transient SimpleIntegerProperty priorityProperty = new SimpleIntegerProperty();
private transient SimpleObjectProperty<Instant> lastSeenProperty = new SimpleObjectProperty<>();
@ -103,11 +105,11 @@ public class JavaFxModel implements Model {
return delegate.toString();
}
public BooleanProperty getOnlineProperty() {
public StringProperty getOnlineProperty() {
return onlineProperty;
}
public BooleanProperty getRecordingProperty() {
public StringProperty getRecordingProperty() {
return recordingProperty;
}
@ -312,4 +314,9 @@ public class JavaFxModel implements Model {
public boolean exists() throws IOException {
return delegate.exists();
}
@Override
public boolean isRecordingTimeLimited() {
return delegate.isRecordingTimeLimited();
}
}

View File

@ -0,0 +1,8 @@
package ctbrec.ui;
public class UnicodeEmoji {
public static final String HEAVY_CHECK_MARK = "";
public static final String CLOCK = "🕒";
}

View File

@ -0,0 +1,41 @@
package ctbrec.ui.action;
import java.io.IOException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.time.Instant;
import java.util.concurrent.CompletableFuture;
import ctbrec.Model;
import ctbrec.recorder.Recorder;
import ctbrec.ui.controls.Dialogs;
import javafx.scene.Cursor;
import javafx.scene.Node;
public class RemoveTimeLimitAction {
private Model selectedModel;
private Node source;
private Recorder recorder;
public RemoveTimeLimitAction(Node source, Model selectedModel, Recorder recorder) {
this.source = source;
this.selectedModel = selectedModel;
this.recorder = recorder;
}
public CompletableFuture<Boolean> execute() {
source.setCursor(Cursor.WAIT);
Instant unlimited = Instant.ofEpochMilli(Model.RECORD_INDEFINITELY);
return CompletableFuture.supplyAsync(() -> {
try {
selectedModel.setRecordUntil(unlimited);
recorder.stopRecordingAt(selectedModel);
return true;
} catch (InvalidKeyException | NoSuchAlgorithmException | IOException e) {
Dialogs.showError(source.getScene(), "Error", "Couln't remove stop date", e);
return false;
}
}).whenComplete((r,e) -> source.setCursor(Cursor.DEFAULT));
}
}

View File

@ -2,6 +2,7 @@ package ctbrec.ui.tabs;
import static ctbrec.Recording.State.*;
import static ctbrec.SubsequentAction.*;
import static ctbrec.ui.UnicodeEmoji.*;
import java.io.IOException;
import java.security.InvalidKeyException;
@ -9,6 +10,9 @@ import java.security.NoSuchAlgorithmException;
import java.time.Instant;
import java.time.LocalDate;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.time.format.FormatStyle;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
@ -44,6 +48,7 @@ import ctbrec.ui.action.FollowAction;
import ctbrec.ui.action.OpenRecordingsDir;
import ctbrec.ui.action.PauseAction;
import ctbrec.ui.action.PlayAction;
import ctbrec.ui.action.RemoveTimeLimitAction;
import ctbrec.ui.action.ResumeAction;
import ctbrec.ui.action.StopRecordingAction;
import ctbrec.ui.action.ToggleRecordingAction;
@ -107,6 +112,8 @@ import javafx.util.converter.NumberStringConverter;
public class RecordedModelsTab extends Tab implements TabSelectionListener {
private static final Logger LOG = LoggerFactory.getLogger(RecordedModelsTab.class);
private static final String STYLE_ALIGN_CENTER = "-fx-alignment: CENTER;";
private ReentrantLock lock = new ReentrantLock();
private ScheduledService<List<JavaFxModel>> updateService;
private Recorder recorder;
@ -168,28 +175,29 @@ public class RecordedModelsTab extends Tab implements TabSelectionListener {
}
TableColumn<JavaFxModel, String> name = new TableColumn<>("Model");
name.setPrefWidth(200);
name.setCellValueFactory(new PropertyValueFactory<JavaFxModel, String>("displayName"));
name.setCellValueFactory(new PropertyValueFactory<>("displayName"));
name.setCellFactory(new ClickableCellFactory<>());
name.setEditable(false);
name.setId("name");
TableColumn<JavaFxModel, String> url = new TableColumn<>("URL");
url.setCellValueFactory(new PropertyValueFactory<JavaFxModel, String>("url"));
url.setCellValueFactory(new PropertyValueFactory<>("url"));
url.setCellFactory(new ClickableCellFactory<>());
url.setPrefWidth(400);
url.setEditable(false);
url.setId("url");
TableColumn<JavaFxModel, Boolean> online = new TableColumn<>("Online");
TableColumn<JavaFxModel, String> online = new TableColumn<>("Online");
online.setCellValueFactory(cdf -> cdf.getValue().getOnlineProperty());
online.setCellFactory(CheckBoxTableCell.forTableColumn(online));
online.setPrefWidth(100);
online.setEditable(false);
online.setId("online");
TableColumn<JavaFxModel, Boolean> recording = new TableColumn<>("Recording");
online.setStyle(STYLE_ALIGN_CENTER);
TableColumn<JavaFxModel, String> recording = new TableColumn<>("Recording");
recording.setCellValueFactory(cdf -> cdf.getValue().getRecordingProperty());
recording.setCellFactory(CheckBoxTableCell.forTableColumn(recording));
recording.setCellFactory(tc -> new RecordingCell());
recording.setPrefWidth(100);
recording.setEditable(false);
recording.setId("recording");
recording.setStyle(STYLE_ALIGN_CENTER);
TableColumn<JavaFxModel, Boolean> paused = new TableColumn<>("Paused");
paused.setCellValueFactory(cdf -> cdf.getValue().getPausedProperty());
paused.setCellFactory(CheckBoxTableCell.forTableColumn(paused));
@ -207,13 +215,13 @@ public class RecordedModelsTab extends Tab implements TabSelectionListener {
priority.setId("priority");
TableColumn<JavaFxModel, Instant> lastSeen = new TableColumn<>("last seen");
lastSeen.setCellValueFactory(cdf -> cdf.getValue().lastSeenProperty());
lastSeen.setCellFactory(new DateTimeCellFactory<JavaFxModel>());
lastSeen.setCellFactory(new DateTimeCellFactory<>());
lastSeen.setPrefWidth(150);
lastSeen.setEditable(false);
lastSeen.setId("lastSeen");
TableColumn<JavaFxModel, Instant> lastRecorded = new TableColumn<>("last recorded");
lastRecorded.setCellValueFactory(cdf -> cdf.getValue().lastRecordedProperty());
lastRecorded.setCellFactory(new DateTimeCellFactory<JavaFxModel>());
lastRecorded.setCellFactory(new DateTimeCellFactory<>());
lastRecorded.setPrefWidth(150);
lastRecorded.setEditable(false);
lastRecorded.setId("lastRecorded");
@ -581,17 +589,21 @@ public class RecordedModelsTab extends Tab implements TabSelectionListener {
return recorder.getModels()
.stream()
.map(JavaFxModel::new)
.peek(fxm -> {
.peek(fxm -> { // NOSONAR
for (Recording recording : recordings) {
if(recording.getStatus() == RECORDING && Objects.equals(recording.getModel(), fxm)){
fxm.getRecordingProperty().set(true);
String recordingValue = HEAVY_CHECK_MARK;
if(!Objects.equals(recording.getModel().getRecordUntil(), Instant.ofEpochMilli(Model.RECORD_INDEFINITELY))) {
recordingValue += ' ' + CLOCK;
}
fxm.getRecordingProperty().set(recordingValue);
break;
}
}
for (Model onlineModel : onlineModels) {
if(Objects.equals(onlineModel, fxm)) {
fxm.getOnlineProperty().set(true);
fxm.getOnlineProperty().set(HEAVY_CHECK_MARK);
break;
}
}
@ -649,6 +661,8 @@ public class RecordedModelsTab extends Tab implements TabSelectionListener {
resumeRecording.setOnAction(e -> resumeRecording(selectedModels));
MenuItem stopRecordingAt = new MenuItem("Stop Recording at Date");
stopRecordingAt.setOnAction(e -> setStopDate(selectedModels.get(0)));
MenuItem removeTimeLimit = new MenuItem("Remove Time Limit");
removeTimeLimit.setOnAction(e -> removeTimeLimit(selectedModels.get(0)));
MenuItem openInBrowser = new MenuItem("Open in Browser");
openInBrowser.setOnAction(e -> DesktopIntegration.open(selectedModels.get(0).getUrl()));
MenuItem openInPlayer = new MenuItem("Open in Player");
@ -669,6 +683,9 @@ public class RecordedModelsTab extends Tab implements TabSelectionListener {
if (selectedModels.size() == 1) {
menu.getItems().add(selectedModels.get(0).isSuspended() ? resumeRecording : pauseRecording);
menu.getItems().add(stopRecordingAt);
if (selectedModels.get(0).isRecordingTimeLimited()) {
menu.getItems().add(removeTimeLimit);
}
} else {
menu.getItems().addAll(resumeRecording, pauseRecording);
}
@ -687,13 +704,13 @@ public class RecordedModelsTab extends Tab implements TabSelectionListener {
private void setStopDate(JavaFxModel model) {
DatePicker datePicker = new DatePicker();
GridPane grid = new GridPane();
grid.setHgap(10);
grid.setVgap(10);
grid.setPadding(new Insets(20, 150, 10, 10));
grid.add(new Label("Stop at"), 0, 0);
grid.add(datePicker, 1, 0);
grid.add(new Label("And then"), 0, 1);
GridPane gridPane = new GridPane();
gridPane.setHgap(10);
gridPane.setVgap(10);
gridPane.setPadding(new Insets(20, 150, 10, 10));
gridPane.add(new Label("Stop at"), 0, 0);
gridPane.add(datePicker, 1, 0);
gridPane.add(new Label("And then"), 0, 1);
ToggleGroup toggleGroup = new ToggleGroup();
RadioButton pauseButton = new RadioButton("pause recording");
pauseButton.setSelected(model.getRecordUntilSubsequentAction() == PAUSE);
@ -705,18 +722,19 @@ public class RecordedModelsTab extends Tab implements TabSelectionListener {
row.getChildren().addAll(pauseButton, removeButton);
HBox.setMargin(pauseButton, new Insets(5));
HBox.setMargin(removeButton, new Insets(5));
grid.add(row, 1, 1);
if (model.getRecordUntil().toEpochMilli() != Model.RECORD_INDEFINITELY) {
gridPane.add(row, 1, 1);
if (model.isRecordingTimeLimited()) {
LocalDate localDate = LocalDate.ofInstant(model.getRecordUntil(), ZoneId.systemDefault());
datePicker.setValue(localDate);
}
boolean userClickedOk = Dialogs.showCustomInput(getTabPane().getScene(), "Stop Recording at", grid);
boolean userClickedOk = Dialogs.showCustomInput(getTabPane().getScene(), "Stop Recording at", gridPane);
if (userClickedOk) {
SubsequentAction action = pauseButton.isSelected() ? PAUSE : REMOVE;
LOG.info("Stop at {} and {}", datePicker.getValue(), action);
Instant stopAt = Instant.from(datePicker.getValue().atStartOfDay().atZone(ZoneId.systemDefault()));
model.setRecordUntil(stopAt);
model.setRecordUntilSubsequentAction(action);
table.refresh();
try {
recorder.stopRecordingAt(model.getDelegate());
} catch (InvalidKeyException | NoSuchAlgorithmException | IOException e) {
@ -725,6 +743,12 @@ public class RecordedModelsTab extends Tab implements TabSelectionListener {
}
}
private void removeTimeLimit(JavaFxModel selectedModel) {
new RemoveTimeLimitAction(table, selectedModel.getDelegate(), recorder) //
.execute() //
.whenComplete((result, exception) -> table.refresh());
}
private void ignore(ObservableList<JavaFxModel> selectedModels) {
for (JavaFxModel fxModel : selectedModels) {
Model modelToIgnore = fxModel.getDelegate();
@ -737,7 +761,7 @@ public class RecordedModelsTab extends Tab implements TabSelectionListener {
}
private void follow(ObservableList<JavaFxModel> selectedModels) {
new FollowAction(getTabPane(), new ArrayList<JavaFxModel>(selectedModels)).execute();
new FollowAction(getTabPane(), new ArrayList<>(selectedModels)).execute();
}
private void notes(ObservableList<JavaFxModel> selectedModels) {
@ -920,4 +944,25 @@ public class RecordedModelsTab extends Tab implements TabSelectionListener {
return tableCell;
}
}
private class RecordingCell extends TableCell<JavaFxModel, String> {
@Override
protected void updateItem(String value, boolean empty) {
super.updateItem(value, empty);
if (value == null) {
setTooltip(null);
setText(null);
} else {
Model m = getTableView().getItems().get(getTableRow().getIndex());
if (m.isRecordingTimeLimited()) {
Tooltip tooltip = new Tooltip();
DateTimeFormatter dtf = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.SHORT);
ZonedDateTime zonedDateTime = m.getRecordUntil().atZone(ZoneId.systemDefault());
tooltip.setText("Recording until " + dtf.format(zonedDateTime));
setTooltip(tooltip);
}
setText(value);
}
}
}
}

View File

@ -237,6 +237,11 @@ public abstract class AbstractModel implements Model {
this.lastRecorded = lastRecorded;
}
@Override
public boolean isRecordingTimeLimited() {
return !getRecordUntil().equals(Instant.ofEpochMilli(RECORD_INDEFINITELY));
}
@Override
public Instant getRecordUntil() {
return Optional.ofNullable(recordUntil).orElse(Instant.ofEpochMilli(RECORD_INDEFINITELY));

View File

@ -130,6 +130,7 @@ public interface Model extends Comparable<Model>, Serializable {
public HttpHeaderFactory getHttpHeaderFactory();
public boolean isRecordingTimeLimited();
public Instant getRecordUntil();
public void setRecordUntil(Instant instant);