Improve UI features for time limited recordings
This commit is contained in:
parent
8fe48f91b7
commit
97715aecc5
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
package ctbrec.ui;
|
||||
|
||||
public class UnicodeEmoji {
|
||||
|
||||
public static final String HEAVY_CHECK_MARK = "✔";
|
||||
public static final String CLOCK = "🕒";
|
||||
|
||||
}
|
|
@ -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));
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
Loading…
Reference in New Issue