implements PR-05d assets activity progress and logs integration
This commit is contained in:
parent
512e4eb49e
commit
f7e0a4043d
@ -0,0 +1,19 @@
|
|||||||
|
package p.studio.controls.shell;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
public record StudioActivityEntry(
|
||||||
|
String source,
|
||||||
|
String message,
|
||||||
|
StudioActivityEntrySeverity severity,
|
||||||
|
boolean sticky) {
|
||||||
|
|
||||||
|
public StudioActivityEntry {
|
||||||
|
source = Objects.requireNonNull(source, "source").trim();
|
||||||
|
message = Objects.requireNonNull(message, "message").trim();
|
||||||
|
Objects.requireNonNull(severity, "severity");
|
||||||
|
if (source.isBlank() || message.isBlank()) {
|
||||||
|
throw new IllegalArgumentException("source and message must not be blank");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,8 @@
|
|||||||
|
package p.studio.controls.shell;
|
||||||
|
|
||||||
|
public enum StudioActivityEntrySeverity {
|
||||||
|
INFO,
|
||||||
|
SUCCESS,
|
||||||
|
WARNING,
|
||||||
|
ERROR
|
||||||
|
}
|
||||||
@ -0,0 +1,30 @@
|
|||||||
|
package p.studio.controls.shell;
|
||||||
|
|
||||||
|
import p.studio.events.*;
|
||||||
|
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
public final class StudioActivityEventMapper {
|
||||||
|
private StudioActivityEventMapper() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Optional<StudioActivityEntry> map(StudioEvent event) {
|
||||||
|
return switch (event) {
|
||||||
|
case StudioProjectOpenedEvent opened ->
|
||||||
|
Optional.of(new StudioActivityEntry("Studio", "Project opened: " + opened.project().name(), StudioActivityEntrySeverity.SUCCESS, false));
|
||||||
|
case StudioProjectLoadingStartedEvent started ->
|
||||||
|
Optional.of(new StudioActivityEntry("Studio", "Project loading started: " + started.project().name(), StudioActivityEntrySeverity.INFO, false));
|
||||||
|
case StudioProjectLoadingCompletedEvent completed ->
|
||||||
|
Optional.of(new StudioActivityEntry("Studio", "Project ready: " + completed.project().name(), StudioActivityEntrySeverity.SUCCESS, false));
|
||||||
|
case StudioProjectLoadingFailedEvent failed ->
|
||||||
|
Optional.of(new StudioActivityEntry("Studio", failed.message(), StudioActivityEntrySeverity.ERROR, true));
|
||||||
|
case StudioAssetsWorkspaceRefreshStartedEvent ignored ->
|
||||||
|
Optional.of(new StudioActivityEntry("Assets", "Asset scan started", StudioActivityEntrySeverity.INFO, false));
|
||||||
|
case StudioAssetsWorkspaceRefreshedEvent refreshed ->
|
||||||
|
Optional.of(new StudioActivityEntry("Assets", refreshed.assetCount() + " assets loaded", StudioActivityEntrySeverity.SUCCESS, false));
|
||||||
|
case StudioAssetsWorkspaceRefreshFailedEvent failed ->
|
||||||
|
Optional.of(new StudioActivityEntry("Assets", failed.message(), StudioActivityEntrySeverity.ERROR, true));
|
||||||
|
default -> Optional.empty();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,166 @@
|
|||||||
|
package p.studio.controls.shell;
|
||||||
|
|
||||||
|
import javafx.application.Platform;
|
||||||
|
import javafx.collections.FXCollections;
|
||||||
|
import javafx.collections.ObservableList;
|
||||||
|
import javafx.geometry.Insets;
|
||||||
|
import javafx.geometry.Pos;
|
||||||
|
import javafx.scene.control.Label;
|
||||||
|
import javafx.scene.control.ListCell;
|
||||||
|
import javafx.scene.control.ListView;
|
||||||
|
import javafx.scene.control.ProgressBar;
|
||||||
|
import javafx.scene.layout.VBox;
|
||||||
|
import p.studio.Container;
|
||||||
|
import p.studio.controls.lifecycle.StudioControlLifecycle;
|
||||||
|
import p.studio.controls.lifecycle.StudioControlLifecycleSupport;
|
||||||
|
import p.studio.events.*;
|
||||||
|
import p.studio.utilities.events.EventSubscription;
|
||||||
|
import p.studio.utilities.i18n.I18n;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public final class StudioActivityFeedControl extends VBox implements StudioControlLifecycle {
|
||||||
|
private final ObservableList<StudioActivityEntry> entries = FXCollections.observableArrayList();
|
||||||
|
private final List<EventSubscription> subscriptions = new ArrayList<>();
|
||||||
|
private final Label progressLabel = new Label();
|
||||||
|
private final ProgressBar progressBar = new ProgressBar();
|
||||||
|
private final ListView<StudioActivityEntry> listView = new ListView<>(entries);
|
||||||
|
|
||||||
|
public StudioActivityFeedControl() {
|
||||||
|
StudioControlLifecycleSupport.install(this, this);
|
||||||
|
getStyleClass().add("studio-activity-feed");
|
||||||
|
setSpacing(10);
|
||||||
|
setPadding(new Insets(12));
|
||||||
|
|
||||||
|
progressLabel.setText(Container.i18n().text(I18n.ACTIVITY_PROGRESS_IDLE));
|
||||||
|
progressLabel.getStyleClass().add("studio-activity-progress-label");
|
||||||
|
progressBar.getStyleClass().add("studio-activity-progress-bar");
|
||||||
|
progressBar.setVisible(false);
|
||||||
|
progressBar.setManaged(false);
|
||||||
|
|
||||||
|
listView.getStyleClass().add("studio-activity-list");
|
||||||
|
listView.setCellFactory(ignored -> new ActivityCell());
|
||||||
|
|
||||||
|
getChildren().addAll(progressLabel, progressBar, listView);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void subscribe() {
|
||||||
|
final StudioEventBus eventBus = Container.events();
|
||||||
|
subscriptions.add(eventBus.subscribe(StudioProjectOpenedEvent.class, this::onEvent));
|
||||||
|
subscriptions.add(eventBus.subscribe(StudioProjectLoadingStartedEvent.class, this::onEvent));
|
||||||
|
subscriptions.add(eventBus.subscribe(StudioProjectLoadingProgressEvent.class, this::onProjectLoadingProgress));
|
||||||
|
subscriptions.add(eventBus.subscribe(StudioProjectLoadingCompletedEvent.class, this::onProjectLoadingCompleted));
|
||||||
|
subscriptions.add(eventBus.subscribe(StudioProjectLoadingFailedEvent.class, this::onProjectLoadingFailed));
|
||||||
|
subscriptions.add(eventBus.subscribe(StudioAssetsWorkspaceRefreshStartedEvent.class, this::onAssetsRefreshStarted));
|
||||||
|
subscriptions.add(eventBus.subscribe(StudioAssetsWorkspaceRefreshedEvent.class, this::onAssetsRefreshFinished));
|
||||||
|
subscriptions.add(eventBus.subscribe(StudioAssetsWorkspaceRefreshFailedEvent.class, this::onAssetsRefreshFailed));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void unsubscribe() {
|
||||||
|
subscriptions.forEach(EventSubscription::unsubscribe);
|
||||||
|
subscriptions.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onEvent(StudioEvent event) {
|
||||||
|
StudioActivityEventMapper.map(event).ifPresent(this::appendEntry);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onProjectLoadingProgress(StudioProjectLoadingProgressEvent event) {
|
||||||
|
Platform.runLater(() -> {
|
||||||
|
progressLabel.setText(event.message());
|
||||||
|
progressBar.setVisible(true);
|
||||||
|
progressBar.setManaged(true);
|
||||||
|
if (event.indeterminate()) {
|
||||||
|
progressBar.setProgress(ProgressBar.INDETERMINATE_PROGRESS);
|
||||||
|
} else {
|
||||||
|
progressBar.setProgress(event.progress());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onProjectLoadingCompleted(StudioProjectLoadingCompletedEvent event) {
|
||||||
|
onEvent(event);
|
||||||
|
clearProgress();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onProjectLoadingFailed(StudioProjectLoadingFailedEvent event) {
|
||||||
|
onEvent(event);
|
||||||
|
clearProgress();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onAssetsRefreshStarted(StudioAssetsWorkspaceRefreshStartedEvent event) {
|
||||||
|
onEvent(event);
|
||||||
|
Platform.runLater(() -> {
|
||||||
|
progressLabel.setText(Container.i18n().text(I18n.ACTIVITY_PROGRESS_ASSETS_REFRESH));
|
||||||
|
progressBar.setVisible(true);
|
||||||
|
progressBar.setManaged(true);
|
||||||
|
progressBar.setProgress(ProgressBar.INDETERMINATE_PROGRESS);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onAssetsRefreshFinished(StudioAssetsWorkspaceRefreshedEvent event) {
|
||||||
|
onEvent(event);
|
||||||
|
clearProgress();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onAssetsRefreshFailed(StudioAssetsWorkspaceRefreshFailedEvent event) {
|
||||||
|
onEvent(event);
|
||||||
|
clearProgress();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void clearProgress() {
|
||||||
|
Platform.runLater(() -> {
|
||||||
|
progressLabel.setText(Container.i18n().text(I18n.ACTIVITY_PROGRESS_IDLE));
|
||||||
|
progressBar.setVisible(false);
|
||||||
|
progressBar.setManaged(false);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void appendEntry(StudioActivityEntry entry) {
|
||||||
|
Platform.runLater(() -> {
|
||||||
|
if (!entries.isEmpty()) {
|
||||||
|
final StudioActivityEntry last = entries.getLast();
|
||||||
|
if (last.source().equals(entry.source())
|
||||||
|
&& last.message().equals(entry.message())
|
||||||
|
&& last.severity() == entry.severity()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
entries.addFirst(entry);
|
||||||
|
while (entries.size() > 100) {
|
||||||
|
entries.removeLast();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class ActivityCell extends ListCell<StudioActivityEntry> {
|
||||||
|
@Override
|
||||||
|
protected void updateItem(StudioActivityEntry item, boolean empty) {
|
||||||
|
super.updateItem(item, empty);
|
||||||
|
getStyleClass().removeAll(
|
||||||
|
"studio-activity-cell-info",
|
||||||
|
"studio-activity-cell-success",
|
||||||
|
"studio-activity-cell-warning",
|
||||||
|
"studio-activity-cell-error");
|
||||||
|
|
||||||
|
if (empty || item == null) {
|
||||||
|
setText(null);
|
||||||
|
setGraphic(null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final Label source = new Label(item.source());
|
||||||
|
source.getStyleClass().add("studio-activity-cell-source");
|
||||||
|
final Label message = new Label(item.message());
|
||||||
|
message.getStyleClass().add("studio-activity-cell-message");
|
||||||
|
message.setWrapText(true);
|
||||||
|
final VBox content = new VBox(4, source, message);
|
||||||
|
content.setAlignment(Pos.CENTER_LEFT);
|
||||||
|
setGraphic(content);
|
||||||
|
getStyleClass().add("studio-activity-cell-" + item.severity().name().toLowerCase());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -117,6 +117,10 @@ public enum I18n {
|
|||||||
ASSETS_LABEL_PRELOAD("assets.label.preload"),
|
ASSETS_LABEL_PRELOAD("assets.label.preload"),
|
||||||
ASSETS_VALUE_YES("assets.value.yes"),
|
ASSETS_VALUE_YES("assets.value.yes"),
|
||||||
ASSETS_VALUE_NO("assets.value.no"),
|
ASSETS_VALUE_NO("assets.value.no"),
|
||||||
|
ASSETS_PROGRESS_IDLE("assets.progress.idle"),
|
||||||
|
ASSETS_PROGRESS_REFRESHING("assets.progress.refreshing"),
|
||||||
|
ASSETS_PROGRESS_LOADING_DETAILS("assets.progress.loadingDetails"),
|
||||||
|
ASSETS_LOGS_TITLE("assets.logs.title"),
|
||||||
ASSETS_INPUTS_EMPTY("assets.inputs.empty"),
|
ASSETS_INPUTS_EMPTY("assets.inputs.empty"),
|
||||||
ASSETS_DIAGNOSTICS_EMPTY("assets.diagnostics.empty"),
|
ASSETS_DIAGNOSTICS_EMPTY("assets.diagnostics.empty"),
|
||||||
ASSETS_PREVIEW_EMPTY("assets.preview.empty"),
|
ASSETS_PREVIEW_EMPTY("assets.preview.empty"),
|
||||||
@ -124,6 +128,8 @@ public enum I18n {
|
|||||||
ASSETS_PREVIEW_IMAGE_ERROR("assets.preview.imageError"),
|
ASSETS_PREVIEW_IMAGE_ERROR("assets.preview.imageError"),
|
||||||
ASSETS_PREVIEW_AUDIO_PLACEHOLDER("assets.preview.audioPlaceholder"),
|
ASSETS_PREVIEW_AUDIO_PLACEHOLDER("assets.preview.audioPlaceholder"),
|
||||||
ASSETS_PREVIEW_GENERIC_PLACEHOLDER("assets.preview.genericPlaceholder"),
|
ASSETS_PREVIEW_GENERIC_PLACEHOLDER("assets.preview.genericPlaceholder"),
|
||||||
|
ACTIVITY_PROGRESS_IDLE("activity.progress.idle"),
|
||||||
|
ACTIVITY_PROGRESS_ASSETS_REFRESH("activity.progress.assetsRefresh"),
|
||||||
ASSETS_SUMMARY_LOADING("assets.summary.loading"),
|
ASSETS_SUMMARY_LOADING("assets.summary.loading"),
|
||||||
ASSETS_SUMMARY_EMPTY("assets.summary.empty"),
|
ASSETS_SUMMARY_EMPTY("assets.summary.empty"),
|
||||||
ASSETS_SUMMARY_READY("assets.summary.ready"),
|
ASSETS_SUMMARY_READY("assets.summary.ready"),
|
||||||
|
|||||||
@ -43,7 +43,7 @@ public final class MainView extends BorderPane {
|
|||||||
setRight(new StudioRightUtilityPanelControl(
|
setRight(new StudioRightUtilityPanelControl(
|
||||||
runSurface,
|
runSurface,
|
||||||
Container.i18n().bind(I18n.SHELL_ACTIVITY),
|
Container.i18n().bind(I18n.SHELL_ACTIVITY),
|
||||||
StudioRightUtilityPanelControl.createPlaceholderContent(Container.i18n().bind(I18n.SHELL_ACTIVITY))));
|
new StudioActivityFeedControl()));
|
||||||
|
|
||||||
// default
|
// default
|
||||||
workspaceRail.select(WorkspaceId.ASSETS);
|
workspaceRail.select(WorkspaceId.ASSETS);
|
||||||
|
|||||||
@ -36,9 +36,12 @@ public final class AssetWorkspace implements Workspace {
|
|||||||
private final FlowPane filterBar = new FlowPane();
|
private final FlowPane filterBar = new FlowPane();
|
||||||
private final Label navigatorStateLabel = new Label();
|
private final Label navigatorStateLabel = new Label();
|
||||||
private final VBox navigatorContent = new VBox(8);
|
private final VBox navigatorContent = new VBox(8);
|
||||||
|
private final Label inlineProgressLabel = new Label();
|
||||||
|
private final ProgressBar inlineProgressBar = new ProgressBar();
|
||||||
|
|
||||||
private final VBox detailsContent = new VBox(12);
|
private final VBox detailsContent = new VBox(12);
|
||||||
private final Label workspaceSummaryLabel = new Label();
|
private final Label workspaceSummaryLabel = new Label();
|
||||||
|
private final TextArea logsArea = new TextArea();
|
||||||
|
|
||||||
private final Map<AssetNavigatorFilter, ToggleButton> filterButtons = new EnumMap<>(AssetNavigatorFilter.class);
|
private final Map<AssetNavigatorFilter, ToggleButton> filterButtons = new EnumMap<>(AssetNavigatorFilter.class);
|
||||||
private final EnumSet<AssetNavigatorFilter> activeFilters = EnumSet.noneOf(AssetNavigatorFilter.class);
|
private final EnumSet<AssetNavigatorFilter> activeFilters = EnumSet.noneOf(AssetNavigatorFilter.class);
|
||||||
@ -61,6 +64,7 @@ public final class AssetWorkspace implements Workspace {
|
|||||||
|
|
||||||
root.getStyleClass().add("assets-workspace");
|
root.getStyleClass().add("assets-workspace");
|
||||||
root.setCenter(buildLayout());
|
root.setCenter(buildLayout());
|
||||||
|
root.setBottom(buildLogsPane());
|
||||||
renderState();
|
renderState();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -88,7 +92,15 @@ public final class AssetWorkspace implements Workspace {
|
|||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
|
||||||
private SplitPane buildLayout() {
|
private VBox buildLayout() {
|
||||||
|
inlineProgressLabel.getStyleClass().add("assets-workspace-inline-progress-label");
|
||||||
|
inlineProgressLabel.setText(Container.i18n().text(I18n.ASSETS_PROGRESS_IDLE));
|
||||||
|
inlineProgressBar.getStyleClass().add("assets-workspace-inline-progress-bar");
|
||||||
|
inlineProgressBar.setVisible(false);
|
||||||
|
inlineProgressBar.setManaged(false);
|
||||||
|
final VBox topProgress = new VBox(6, inlineProgressLabel, inlineProgressBar);
|
||||||
|
topProgress.getStyleClass().add("assets-workspace-inline-progress");
|
||||||
|
|
||||||
final VBox navigatorPane = new VBox(8);
|
final VBox navigatorPane = new VBox(8);
|
||||||
navigatorPane.getStyleClass().add("assets-workspace-pane");
|
navigatorPane.getStyleClass().add("assets-workspace-pane");
|
||||||
final Label navigatorTitle = new Label();
|
final Label navigatorTitle = new Label();
|
||||||
@ -133,7 +145,24 @@ public final class AssetWorkspace implements Workspace {
|
|||||||
final SplitPane splitPane = new SplitPane(navigatorPane, detailsPane);
|
final SplitPane splitPane = new SplitPane(navigatorPane, detailsPane);
|
||||||
splitPane.setDividerPositions(0.34);
|
splitPane.setDividerPositions(0.34);
|
||||||
splitPane.getStyleClass().add("assets-workspace-split");
|
splitPane.getStyleClass().add("assets-workspace-split");
|
||||||
return splitPane;
|
final VBox layout = new VBox(10, topProgress, splitPane);
|
||||||
|
VBox.setVgrow(splitPane, Priority.ALWAYS);
|
||||||
|
return layout;
|
||||||
|
}
|
||||||
|
|
||||||
|
private TitledPane buildLogsPane() {
|
||||||
|
logsArea.setEditable(false);
|
||||||
|
logsArea.setWrapText(true);
|
||||||
|
logsArea.setPrefRowCount(8);
|
||||||
|
logsArea.getStyleClass().add("assets-workspace-logs");
|
||||||
|
|
||||||
|
final TitledPane pane = new TitledPane();
|
||||||
|
pane.textProperty().bind(Container.i18n().bind(I18n.ASSETS_LOGS_TITLE));
|
||||||
|
pane.setContent(logsArea);
|
||||||
|
pane.setCollapsible(true);
|
||||||
|
pane.setExpanded(true);
|
||||||
|
pane.getStyleClass().add("assets-workspace-logs-pane");
|
||||||
|
return pane;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void configureFilterBar() {
|
private void configureFilterBar() {
|
||||||
@ -169,6 +198,8 @@ public final class AssetWorkspace implements Workspace {
|
|||||||
selectedAssetDetails = null;
|
selectedAssetDetails = null;
|
||||||
detailsErrorMessage = null;
|
detailsErrorMessage = null;
|
||||||
selectedPreviewInput = null;
|
selectedPreviewInput = null;
|
||||||
|
setInlineProgress(Container.i18n().text(I18n.ASSETS_PROGRESS_REFRESHING), ProgressBar.INDETERMINATE_PROGRESS, true);
|
||||||
|
appendLog("Assets refresh started.");
|
||||||
renderState();
|
renderState();
|
||||||
workspaceBus.publish(new StudioAssetsWorkspaceRefreshStartedEvent(projectReference));
|
workspaceBus.publish(new StudioAssetsWorkspaceRefreshStartedEvent(projectReference));
|
||||||
|
|
||||||
@ -179,12 +210,16 @@ public final class AssetWorkspace implements Workspace {
|
|||||||
state = AssetWorkspaceState.error(state, rootCauseMessage(throwable));
|
state = AssetWorkspaceState.error(state, rootCauseMessage(throwable));
|
||||||
detailsStatus = AssetWorkspaceDetailsStatus.ERROR;
|
detailsStatus = AssetWorkspaceDetailsStatus.ERROR;
|
||||||
detailsErrorMessage = state.errorMessage();
|
detailsErrorMessage = state.errorMessage();
|
||||||
|
setInlineProgress(Container.i18n().text(I18n.ASSETS_PROGRESS_IDLE), 0, false);
|
||||||
|
appendLog("Assets refresh failed: " + state.errorMessage());
|
||||||
renderState();
|
renderState();
|
||||||
workspaceBus.publish(new StudioAssetsWorkspaceRefreshFailedEvent(projectReference, state.errorMessage()));
|
workspaceBus.publish(new StudioAssetsWorkspaceRefreshFailedEvent(projectReference, state.errorMessage()));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
state = AssetWorkspaceState.ready(snapshot.assets(), state.selectedKey());
|
state = AssetWorkspaceState.ready(snapshot.assets(), state.selectedKey());
|
||||||
|
setInlineProgress(Container.i18n().text(I18n.ASSETS_PROGRESS_IDLE), 0, false);
|
||||||
|
appendLog("Assets refresh completed: " + state.assets().size() + " assets.");
|
||||||
renderState();
|
renderState();
|
||||||
workspaceBus.publish(new StudioAssetsWorkspaceRefreshedEvent(projectReference, state.assets().size()));
|
workspaceBus.publish(new StudioAssetsWorkspaceRefreshedEvent(projectReference, state.assets().size()));
|
||||||
state.selectedAsset().ifPresent(asset -> {
|
state.selectedAsset().ifPresent(asset -> {
|
||||||
@ -199,6 +234,8 @@ public final class AssetWorkspace implements Workspace {
|
|||||||
selectedAssetDetails = null;
|
selectedAssetDetails = null;
|
||||||
detailsErrorMessage = null;
|
detailsErrorMessage = null;
|
||||||
selectedPreviewInput = null;
|
selectedPreviewInput = null;
|
||||||
|
setInlineProgress(Container.i18n().text(I18n.ASSETS_PROGRESS_LOADING_DETAILS), ProgressBar.INDETERMINATE_PROGRESS, true);
|
||||||
|
appendLog("Loading details for " + selectionKey.stableKey() + ".");
|
||||||
renderState();
|
renderState();
|
||||||
|
|
||||||
CompletableFuture
|
CompletableFuture
|
||||||
@ -211,6 +248,8 @@ public final class AssetWorkspace implements Workspace {
|
|||||||
detailsStatus = AssetWorkspaceDetailsStatus.ERROR;
|
detailsStatus = AssetWorkspaceDetailsStatus.ERROR;
|
||||||
detailsErrorMessage = rootCauseMessage(throwable);
|
detailsErrorMessage = rootCauseMessage(throwable);
|
||||||
selectedAssetDetails = null;
|
selectedAssetDetails = null;
|
||||||
|
setInlineProgress(Container.i18n().text(I18n.ASSETS_PROGRESS_IDLE), 0, false);
|
||||||
|
appendLog("Asset details failed: " + detailsErrorMessage);
|
||||||
renderState();
|
renderState();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -218,6 +257,8 @@ public final class AssetWorkspace implements Workspace {
|
|||||||
selectedAssetDetails = details;
|
selectedAssetDetails = details;
|
||||||
detailsStatus = AssetWorkspaceDetailsStatus.READY;
|
detailsStatus = AssetWorkspaceDetailsStatus.READY;
|
||||||
selectedPreviewInput = firstPreviewInput(details);
|
selectedPreviewInput = firstPreviewInput(details);
|
||||||
|
setInlineProgress(Container.i18n().text(I18n.ASSETS_PROGRESS_IDLE), 0, false);
|
||||||
|
appendLog("Asset details ready for " + details.summary().assetName() + ".");
|
||||||
renderState();
|
renderState();
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
@ -617,6 +658,7 @@ public final class AssetWorkspace implements Workspace {
|
|||||||
|
|
||||||
private void selectAsset(AssetWorkspaceSelectionKey selectionKey) {
|
private void selectAsset(AssetWorkspaceSelectionKey selectionKey) {
|
||||||
state = state.withSelection(selectionKey);
|
state = state.withSelection(selectionKey);
|
||||||
|
appendLog("Selected asset " + selectionKey.stableKey() + ".");
|
||||||
renderState();
|
renderState();
|
||||||
workspaceBus.publish(new StudioAssetsWorkspaceSelectionChangedEvent(projectReference, selectionKey));
|
workspaceBus.publish(new StudioAssetsWorkspaceSelectionChangedEvent(projectReference, selectionKey));
|
||||||
loadSelectedAssetDetails(selectionKey);
|
loadSelectedAssetDetails(selectionKey);
|
||||||
@ -698,6 +740,17 @@ public final class AssetWorkspace implements Workspace {
|
|||||||
return projectReference.rootPath().resolve("assets").toAbsolutePath().normalize();
|
return projectReference.rootPath().resolve("assets").toAbsolutePath().normalize();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void setInlineProgress(String message, double progress, boolean visible) {
|
||||||
|
inlineProgressLabel.setText(message);
|
||||||
|
inlineProgressBar.setVisible(visible);
|
||||||
|
inlineProgressBar.setManaged(visible);
|
||||||
|
inlineProgressBar.setProgress(progress);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void appendLog(String message) {
|
||||||
|
logsArea.appendText(message + System.lineSeparator());
|
||||||
|
}
|
||||||
|
|
||||||
private String rootCauseMessage(Throwable throwable) {
|
private String rootCauseMessage(Throwable throwable) {
|
||||||
Throwable current = throwable;
|
Throwable current = throwable;
|
||||||
while (current.getCause() != null) {
|
while (current.getCause() != null) {
|
||||||
|
|||||||
@ -107,6 +107,10 @@ assets.label.codec=Codec
|
|||||||
assets.label.preload=Preload
|
assets.label.preload=Preload
|
||||||
assets.value.yes=Yes
|
assets.value.yes=Yes
|
||||||
assets.value.no=No
|
assets.value.no=No
|
||||||
|
assets.progress.idle=Assets workspace idle.
|
||||||
|
assets.progress.refreshing=Refreshing assets...
|
||||||
|
assets.progress.loadingDetails=Loading selected asset details...
|
||||||
|
assets.logs.title=Logs
|
||||||
assets.inputs.empty=No previewable inputs are currently declared for this asset.
|
assets.inputs.empty=No previewable inputs are currently declared for this asset.
|
||||||
assets.diagnostics.empty=No diagnostics are currently attached to this asset.
|
assets.diagnostics.empty=No diagnostics are currently attached to this asset.
|
||||||
assets.preview.empty=Select an input to preview it here.
|
assets.preview.empty=Select an input to preview it here.
|
||||||
@ -114,6 +118,8 @@ assets.preview.textError=Unable to read this text-like input for preview.
|
|||||||
assets.preview.imageError=Unable to decode this image for preview.
|
assets.preview.imageError=Unable to decode this image for preview.
|
||||||
assets.preview.audioPlaceholder=Audio preview placeholder: {0}
|
assets.preview.audioPlaceholder=Audio preview placeholder: {0}
|
||||||
assets.preview.genericPlaceholder=Preview placeholder for {0}
|
assets.preview.genericPlaceholder=Preview placeholder for {0}
|
||||||
|
activity.progress.idle=No background work in progress.
|
||||||
|
activity.progress.assetsRefresh=Refreshing asset workspace...
|
||||||
assets.summary.loading=Hydrating asset workspace state...
|
assets.summary.loading=Hydrating asset workspace state...
|
||||||
assets.summary.empty=No assets are currently available.
|
assets.summary.empty=No assets are currently available.
|
||||||
assets.summary.ready=Navigator ready with {0} assets.
|
assets.summary.ready=Navigator ready with {0} assets.
|
||||||
|
|||||||
@ -113,6 +113,56 @@
|
|||||||
-fx-padding: 16;
|
-fx-padding: 16;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.studio-activity-feed {
|
||||||
|
-fx-background-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.studio-activity-progress-label {
|
||||||
|
-fx-text-fill: #d4e0ec;
|
||||||
|
-fx-font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.studio-activity-progress-bar {
|
||||||
|
-fx-accent: #5cb6ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.studio-activity-list {
|
||||||
|
-fx-background-color: transparent;
|
||||||
|
-fx-control-inner-background: #12161b;
|
||||||
|
}
|
||||||
|
|
||||||
|
.studio-activity-list .list-cell {
|
||||||
|
-fx-background-color: transparent;
|
||||||
|
-fx-padding: 8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.studio-activity-cell-source {
|
||||||
|
-fx-text-fill: #8db7e1;
|
||||||
|
-fx-font-size: 11px;
|
||||||
|
-fx-font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.studio-activity-cell-message {
|
||||||
|
-fx-text-fill: #f1f6fb;
|
||||||
|
-fx-font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.studio-activity-cell-info {
|
||||||
|
-fx-background-color: #16222f;
|
||||||
|
}
|
||||||
|
|
||||||
|
.studio-activity-cell-success {
|
||||||
|
-fx-background-color: #143020;
|
||||||
|
}
|
||||||
|
|
||||||
|
.studio-activity-cell-warning {
|
||||||
|
-fx-background-color: #3f3115;
|
||||||
|
}
|
||||||
|
|
||||||
|
.studio-activity-cell-error {
|
||||||
|
-fx-background-color: #40191d;
|
||||||
|
}
|
||||||
|
|
||||||
.assets-workspace {
|
.assets-workspace {
|
||||||
-fx-background-color: #17191d;
|
-fx-background-color: #17191d;
|
||||||
}
|
}
|
||||||
@ -127,6 +177,19 @@
|
|||||||
-fx-background-color: #1b1f25;
|
-fx-background-color: #1b1f25;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.assets-workspace-inline-progress {
|
||||||
|
-fx-padding: 16 18 0 18;
|
||||||
|
}
|
||||||
|
|
||||||
|
.assets-workspace-inline-progress-label {
|
||||||
|
-fx-text-fill: #d4dce5;
|
||||||
|
-fx-font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.assets-workspace-inline-progress-bar {
|
||||||
|
-fx-accent: #5cb6ff;
|
||||||
|
}
|
||||||
|
|
||||||
.assets-workspace-pane-title {
|
.assets-workspace-pane-title {
|
||||||
-fx-text-fill: #f3f7fb;
|
-fx-text-fill: #f3f7fb;
|
||||||
-fx-font-size: 15px;
|
-fx-font-size: 15px;
|
||||||
@ -417,6 +480,16 @@
|
|||||||
-fx-text-fill: #ffd6d9;
|
-fx-text-fill: #ffd6d9;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.assets-workspace-logs-pane {
|
||||||
|
-fx-collapsible: true;
|
||||||
|
}
|
||||||
|
|
||||||
|
.assets-workspace-logs {
|
||||||
|
-fx-control-inner-background: #0f1318;
|
||||||
|
-fx-text-fill: #dbe4ed;
|
||||||
|
-fx-highlight-fill: #2c5e91;
|
||||||
|
}
|
||||||
|
|
||||||
.studio-project-launcher {
|
.studio-project-launcher {
|
||||||
-fx-background-color: linear-gradient(to bottom, #20242c, #14181d);
|
-fx-background-color: linear-gradient(to bottom, #20242c, #14181d);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,51 @@
|
|||||||
|
package p.studio.controls.shell;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import p.studio.events.StudioAssetsWorkspaceRefreshFailedEvent;
|
||||||
|
import p.studio.events.StudioAssetsWorkspaceRefreshedEvent;
|
||||||
|
import p.studio.events.StudioProjectOpenedEvent;
|
||||||
|
import p.studio.projects.ProjectReference;
|
||||||
|
|
||||||
|
import java.nio.file.Path;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
|
final class StudioActivityEventMapperTest {
|
||||||
|
@Test
|
||||||
|
void mapsProjectOpenedToSuccessEntry() {
|
||||||
|
final StudioActivityEntry entry = StudioActivityEventMapper
|
||||||
|
.map(new StudioProjectOpenedEvent(project()))
|
||||||
|
.orElseThrow();
|
||||||
|
|
||||||
|
assertEquals("Studio", entry.source());
|
||||||
|
assertEquals(StudioActivityEntrySeverity.SUCCESS, entry.severity());
|
||||||
|
assertTrue(entry.message().contains("Project opened"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void mapsAssetsRefreshedToSuccessEntry() {
|
||||||
|
final StudioActivityEntry entry = StudioActivityEventMapper
|
||||||
|
.map(new StudioAssetsWorkspaceRefreshedEvent(project(), 7))
|
||||||
|
.orElseThrow();
|
||||||
|
|
||||||
|
assertEquals("Assets", entry.source());
|
||||||
|
assertEquals(StudioActivityEntrySeverity.SUCCESS, entry.severity());
|
||||||
|
assertEquals("7 assets loaded", entry.message());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void mapsRefreshFailureToStickyErrorEntry() {
|
||||||
|
final StudioActivityEntry entry = StudioActivityEventMapper
|
||||||
|
.map(new StudioAssetsWorkspaceRefreshFailedEvent(project(), "Refresh failed"))
|
||||||
|
.orElseThrow();
|
||||||
|
|
||||||
|
assertEquals(StudioActivityEntrySeverity.ERROR, entry.severity());
|
||||||
|
assertTrue(entry.sticky());
|
||||||
|
assertEquals("Refresh failed", entry.message());
|
||||||
|
}
|
||||||
|
|
||||||
|
private ProjectReference project() {
|
||||||
|
return new ProjectReference("Main", "1.0.0", "pbs", 1, Path.of("/tmp/main"));
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user