implements PR-05c selected asset details contract and preview
This commit is contained in:
parent
17064aa505
commit
512e4eb49e
@ -94,6 +94,36 @@ public enum I18n {
|
|||||||
ASSETS_BADGE_ORPHAN("assets.badge.orphan"),
|
ASSETS_BADGE_ORPHAN("assets.badge.orphan"),
|
||||||
ASSETS_BADGE_PRELOAD("assets.badge.preload"),
|
ASSETS_BADGE_PRELOAD("assets.badge.preload"),
|
||||||
ASSETS_BADGE_DIAGNOSTICS("assets.badge.diagnostics"),
|
ASSETS_BADGE_DIAGNOSTICS("assets.badge.diagnostics"),
|
||||||
|
ASSETS_SECTION_SUMMARY("assets.section.summary"),
|
||||||
|
ASSETS_SECTION_RUNTIME_CONTRACT("assets.section.runtimeContract"),
|
||||||
|
ASSETS_SECTION_INPUTS_PREVIEW("assets.section.inputsPreview"),
|
||||||
|
ASSETS_SECTION_DIAGNOSTICS("assets.section.diagnostics"),
|
||||||
|
ASSETS_SECTION_ACTIONS("assets.section.actions"),
|
||||||
|
ASSETS_ACTIONS_PRIMARY("assets.actions.primary"),
|
||||||
|
ASSETS_ACTIONS_SENSITIVE("assets.actions.sensitive"),
|
||||||
|
ASSETS_ACTION_DOCTOR("assets.action.doctor"),
|
||||||
|
ASSETS_ACTION_BUILD("assets.action.build"),
|
||||||
|
ASSETS_ACTION_ADOPT("assets.action.adopt"),
|
||||||
|
ASSETS_ACTION_REGISTER("assets.action.register"),
|
||||||
|
ASSETS_ACTION_FORGET("assets.action.forget"),
|
||||||
|
ASSETS_ACTION_REMOVE("assets.action.remove"),
|
||||||
|
ASSETS_LABEL_NAME("assets.label.name"),
|
||||||
|
ASSETS_LABEL_STATE("assets.label.state"),
|
||||||
|
ASSETS_LABEL_ASSET_ID("assets.label.assetId"),
|
||||||
|
ASSETS_LABEL_TYPE("assets.label.type"),
|
||||||
|
ASSETS_LABEL_LOCATION("assets.label.location"),
|
||||||
|
ASSETS_LABEL_FORMAT("assets.label.format"),
|
||||||
|
ASSETS_LABEL_CODEC("assets.label.codec"),
|
||||||
|
ASSETS_LABEL_PRELOAD("assets.label.preload"),
|
||||||
|
ASSETS_VALUE_YES("assets.value.yes"),
|
||||||
|
ASSETS_VALUE_NO("assets.value.no"),
|
||||||
|
ASSETS_INPUTS_EMPTY("assets.inputs.empty"),
|
||||||
|
ASSETS_DIAGNOSTICS_EMPTY("assets.diagnostics.empty"),
|
||||||
|
ASSETS_PREVIEW_EMPTY("assets.preview.empty"),
|
||||||
|
ASSETS_PREVIEW_TEXT_ERROR("assets.preview.textError"),
|
||||||
|
ASSETS_PREVIEW_IMAGE_ERROR("assets.preview.imageError"),
|
||||||
|
ASSETS_PREVIEW_AUDIO_PLACEHOLDER("assets.preview.audioPlaceholder"),
|
||||||
|
ASSETS_PREVIEW_GENERIC_PLACEHOLDER("assets.preview.genericPlaceholder"),
|
||||||
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"),
|
||||||
|
|||||||
@ -2,8 +2,12 @@ package p.studio.workspaces.assets;
|
|||||||
|
|
||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
import javafx.geometry.Insets;
|
import javafx.geometry.Insets;
|
||||||
|
import javafx.geometry.Orientation;
|
||||||
|
import javafx.geometry.Pos;
|
||||||
import javafx.scene.Node;
|
import javafx.scene.Node;
|
||||||
import javafx.scene.control.*;
|
import javafx.scene.control.*;
|
||||||
|
import javafx.scene.image.Image;
|
||||||
|
import javafx.scene.image.ImageView;
|
||||||
import javafx.scene.layout.*;
|
import javafx.scene.layout.*;
|
||||||
import p.studio.Container;
|
import p.studio.Container;
|
||||||
import p.studio.events.*;
|
import p.studio.events.*;
|
||||||
@ -12,36 +16,47 @@ import p.studio.utilities.i18n.I18n;
|
|||||||
import p.studio.workspaces.Workspace;
|
import p.studio.workspaces.Workspace;
|
||||||
import p.studio.workspaces.WorkspaceId;
|
import p.studio.workspaces.WorkspaceId;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.concurrent.CompletableFuture;
|
|
||||||
import java.util.EnumMap;
|
import java.util.EnumMap;
|
||||||
import java.util.EnumSet;
|
import java.util.EnumSet;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
|
||||||
public final class AssetWorkspace implements Workspace {
|
public final class AssetWorkspace implements Workspace {
|
||||||
private final BorderPane root = new BorderPane();
|
private final BorderPane root = new BorderPane();
|
||||||
private final ProjectReference projectReference;
|
private final ProjectReference projectReference;
|
||||||
private final AssetWorkspaceService assetWorkspaceService;
|
private final AssetWorkspaceService assetWorkspaceService;
|
||||||
private final StudioWorkspaceEventBus workspaceBus;
|
private final StudioWorkspaceEventBus workspaceBus;
|
||||||
|
|
||||||
private final TextField searchField = new TextField();
|
private final TextField searchField = new TextField();
|
||||||
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 detailStateLabel = new Label();
|
|
||||||
|
private final VBox detailsContent = new VBox(12);
|
||||||
private final Label workspaceSummaryLabel = new Label();
|
private final Label workspaceSummaryLabel = new Label();
|
||||||
|
|
||||||
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);
|
||||||
|
|
||||||
private String searchQuery = "";
|
|
||||||
private volatile AssetWorkspaceState state = AssetWorkspaceState.loading(null);
|
private volatile AssetWorkspaceState state = AssetWorkspaceState.loading(null);
|
||||||
|
private volatile AssetWorkspaceDetailsStatus detailsStatus = AssetWorkspaceDetailsStatus.EMPTY;
|
||||||
|
private volatile AssetWorkspaceAssetDetails selectedAssetDetails;
|
||||||
|
private volatile String detailsErrorMessage;
|
||||||
|
private volatile Path selectedPreviewInput;
|
||||||
|
private String searchQuery = "";
|
||||||
|
|
||||||
public AssetWorkspace(ProjectReference projectReference) {
|
public AssetWorkspace(ProjectReference projectReference) {
|
||||||
this(projectReference, new FileSystemAssetWorkspaceService());
|
this(projectReference, new FileSystemAssetWorkspaceService());
|
||||||
}
|
}
|
||||||
|
|
||||||
public AssetWorkspace(ProjectReference projectReference, AssetWorkspaceService assetWorkspaceService) {
|
public AssetWorkspace(ProjectReference projectReference, AssetWorkspaceService assetWorkspaceService) {
|
||||||
this.projectReference = projectReference;
|
this.projectReference = Objects.requireNonNull(projectReference, "projectReference");
|
||||||
this.assetWorkspaceService = assetWorkspaceService;
|
this.assetWorkspaceService = Objects.requireNonNull(assetWorkspaceService, "assetWorkspaceService");
|
||||||
this.workspaceBus = new StudioWorkspaceEventBus(WorkspaceId.ASSETS, Container.events());
|
this.workspaceBus = new StudioWorkspaceEventBus(WorkspaceId.ASSETS, Container.events());
|
||||||
|
|
||||||
root.getStyleClass().add("assets-workspace");
|
root.getStyleClass().add("assets-workspace");
|
||||||
@ -75,20 +90,22 @@ public final class AssetWorkspace implements Workspace {
|
|||||||
|
|
||||||
private SplitPane buildLayout() {
|
private SplitPane buildLayout() {
|
||||||
final VBox navigatorPane = new VBox(8);
|
final VBox navigatorPane = new VBox(8);
|
||||||
|
navigatorPane.getStyleClass().add("assets-workspace-pane");
|
||||||
final Label navigatorTitle = new Label();
|
final Label navigatorTitle = new Label();
|
||||||
navigatorTitle.textProperty().bind(Container.i18n().bind(I18n.ASSETS_NAVIGATOR_TITLE));
|
navigatorTitle.textProperty().bind(Container.i18n().bind(I18n.ASSETS_NAVIGATOR_TITLE));
|
||||||
navigatorTitle.getStyleClass().add("assets-workspace-pane-title");
|
navigatorTitle.getStyleClass().add("assets-workspace-pane-title");
|
||||||
|
|
||||||
searchField.setPromptText(Container.i18n().text(I18n.ASSETS_SEARCH_PROMPT));
|
searchField.setPromptText(Container.i18n().text(I18n.ASSETS_SEARCH_PROMPT));
|
||||||
searchField.getStyleClass().add("assets-workspace-search");
|
searchField.getStyleClass().add("assets-workspace-search");
|
||||||
searchField.textProperty().addListener((ignored, oldValue, newValue) -> {
|
searchField.textProperty().addListener((ignored, oldValue, newValue) -> {
|
||||||
final String previous = oldValue == null ? "" : oldValue;
|
final String previous = oldValue == null ? "" : oldValue;
|
||||||
final String current = newValue == null ? "" : newValue;
|
final String current = newValue == null ? "" : newValue;
|
||||||
if (previous.equals(current)) {
|
if (!previous.equals(current)) {
|
||||||
return;
|
|
||||||
}
|
|
||||||
searchQuery = current;
|
searchQuery = current;
|
||||||
renderState();
|
renderState();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
configureFilterBar();
|
configureFilterBar();
|
||||||
navigatorStateLabel.getStyleClass().add("assets-workspace-pane-body");
|
navigatorStateLabel.getStyleClass().add("assets-workspace-pane-body");
|
||||||
navigatorContent.getStyleClass().add("assets-workspace-navigator-content");
|
navigatorContent.getStyleClass().add("assets-workspace-navigator-content");
|
||||||
@ -96,19 +113,22 @@ public final class AssetWorkspace implements Workspace {
|
|||||||
navigatorScroll.setFitToWidth(true);
|
navigatorScroll.setFitToWidth(true);
|
||||||
navigatorScroll.setHbarPolicy(ScrollPane.ScrollBarPolicy.NEVER);
|
navigatorScroll.setHbarPolicy(ScrollPane.ScrollBarPolicy.NEVER);
|
||||||
navigatorScroll.getStyleClass().add("assets-workspace-navigator-scroll");
|
navigatorScroll.getStyleClass().add("assets-workspace-navigator-scroll");
|
||||||
navigatorPane.getStyleClass().add("assets-workspace-pane");
|
|
||||||
navigatorPane.getChildren().addAll(navigatorTitle, searchField, filterBar, navigatorStateLabel, navigatorScroll);
|
navigatorPane.getChildren().addAll(navigatorTitle, searchField, filterBar, navigatorStateLabel, navigatorScroll);
|
||||||
VBox.setVgrow(navigatorScroll, Priority.ALWAYS);
|
VBox.setVgrow(navigatorScroll, Priority.ALWAYS);
|
||||||
|
|
||||||
final VBox detailsPane = new VBox(8);
|
final VBox detailsPane = new VBox(10);
|
||||||
|
detailsPane.getStyleClass().add("assets-workspace-pane");
|
||||||
final Label detailsTitle = new Label();
|
final Label detailsTitle = new Label();
|
||||||
detailsTitle.textProperty().bind(Container.i18n().bind(I18n.ASSETS_DETAILS_TITLE));
|
detailsTitle.textProperty().bind(Container.i18n().bind(I18n.ASSETS_DETAILS_TITLE));
|
||||||
detailsTitle.getStyleClass().add("assets-workspace-pane-title");
|
detailsTitle.getStyleClass().add("assets-workspace-pane-title");
|
||||||
workspaceSummaryLabel.getStyleClass().add("assets-workspace-summary");
|
workspaceSummaryLabel.getStyleClass().add("assets-workspace-summary");
|
||||||
detailStateLabel.getStyleClass().add("assets-workspace-pane-body");
|
detailsContent.getStyleClass().add("assets-workspace-details-content");
|
||||||
detailsPane.getStyleClass().add("assets-workspace-pane");
|
final ScrollPane detailsScroll = new ScrollPane(detailsContent);
|
||||||
detailsPane.getChildren().addAll(detailsTitle, workspaceSummaryLabel, detailStateLabel);
|
detailsScroll.setFitToWidth(true);
|
||||||
VBox.setVgrow(detailStateLabel, Priority.ALWAYS);
|
detailsScroll.setHbarPolicy(ScrollPane.ScrollBarPolicy.NEVER);
|
||||||
|
detailsScroll.getStyleClass().add("assets-workspace-details-scroll");
|
||||||
|
detailsPane.getChildren().addAll(detailsTitle, workspaceSummaryLabel, detailsScroll);
|
||||||
|
VBox.setVgrow(detailsScroll, Priority.ALWAYS);
|
||||||
|
|
||||||
final SplitPane splitPane = new SplitPane(navigatorPane, detailsPane);
|
final SplitPane splitPane = new SplitPane(navigatorPane, detailsPane);
|
||||||
splitPane.setDividerPositions(0.34);
|
splitPane.setDividerPositions(0.34);
|
||||||
@ -145,6 +165,10 @@ public final class AssetWorkspace implements Workspace {
|
|||||||
|
|
||||||
private void refresh() {
|
private void refresh() {
|
||||||
state = AssetWorkspaceState.loading(state);
|
state = AssetWorkspaceState.loading(state);
|
||||||
|
detailsStatus = AssetWorkspaceDetailsStatus.EMPTY;
|
||||||
|
selectedAssetDetails = null;
|
||||||
|
detailsErrorMessage = null;
|
||||||
|
selectedPreviewInput = null;
|
||||||
renderState();
|
renderState();
|
||||||
workspaceBus.publish(new StudioAssetsWorkspaceRefreshStartedEvent(projectReference));
|
workspaceBus.publish(new StudioAssetsWorkspaceRefreshStartedEvent(projectReference));
|
||||||
|
|
||||||
@ -153,6 +177,8 @@ public final class AssetWorkspace implements Workspace {
|
|||||||
.whenComplete((snapshot, throwable) -> Platform.runLater(() -> {
|
.whenComplete((snapshot, throwable) -> Platform.runLater(() -> {
|
||||||
if (throwable != null) {
|
if (throwable != null) {
|
||||||
state = AssetWorkspaceState.error(state, rootCauseMessage(throwable));
|
state = AssetWorkspaceState.error(state, rootCauseMessage(throwable));
|
||||||
|
detailsStatus = AssetWorkspaceDetailsStatus.ERROR;
|
||||||
|
detailsErrorMessage = state.errorMessage();
|
||||||
renderState();
|
renderState();
|
||||||
workspaceBus.publish(new StudioAssetsWorkspaceRefreshFailedEvent(projectReference, state.errorMessage()));
|
workspaceBus.publish(new StudioAssetsWorkspaceRefreshFailedEvent(projectReference, state.errorMessage()));
|
||||||
return;
|
return;
|
||||||
@ -161,33 +187,61 @@ public final class AssetWorkspace implements Workspace {
|
|||||||
state = AssetWorkspaceState.ready(snapshot.assets(), state.selectedKey());
|
state = AssetWorkspaceState.ready(snapshot.assets(), state.selectedKey());
|
||||||
renderState();
|
renderState();
|
||||||
workspaceBus.publish(new StudioAssetsWorkspaceRefreshedEvent(projectReference, state.assets().size()));
|
workspaceBus.publish(new StudioAssetsWorkspaceRefreshedEvent(projectReference, state.assets().size()));
|
||||||
state.selectedAsset()
|
state.selectedAsset().ifPresent(asset -> {
|
||||||
.ifPresent(asset -> workspaceBus.publish(new StudioAssetsWorkspaceSelectionChangedEvent(projectReference, asset.selectionKey())));
|
workspaceBus.publish(new StudioAssetsWorkspaceSelectionChangedEvent(projectReference, asset.selectionKey()));
|
||||||
|
loadSelectedAssetDetails(asset.selectionKey());
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void loadSelectedAssetDetails(AssetWorkspaceSelectionKey selectionKey) {
|
||||||
|
detailsStatus = AssetWorkspaceDetailsStatus.LOADING;
|
||||||
|
selectedAssetDetails = null;
|
||||||
|
detailsErrorMessage = null;
|
||||||
|
selectedPreviewInput = null;
|
||||||
|
renderState();
|
||||||
|
|
||||||
|
CompletableFuture
|
||||||
|
.supplyAsync(() -> assetWorkspaceService.loadAssetDetails(projectReference, selectionKey))
|
||||||
|
.whenComplete((details, throwable) -> Platform.runLater(() -> {
|
||||||
|
if (!selectionKey.equals(state.selectedKey())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (throwable != null) {
|
||||||
|
detailsStatus = AssetWorkspaceDetailsStatus.ERROR;
|
||||||
|
detailsErrorMessage = rootCauseMessage(throwable);
|
||||||
|
selectedAssetDetails = null;
|
||||||
|
renderState();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
selectedAssetDetails = details;
|
||||||
|
detailsStatus = AssetWorkspaceDetailsStatus.READY;
|
||||||
|
selectedPreviewInput = firstPreviewInput(details);
|
||||||
|
renderState();
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void renderState() {
|
private void renderState() {
|
||||||
|
renderNavigator();
|
||||||
|
renderDetails();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void renderNavigator() {
|
||||||
switch (state.status()) {
|
switch (state.status()) {
|
||||||
case LOADING -> {
|
case LOADING -> {
|
||||||
navigatorStateLabel.setText(Container.i18n().text(I18n.ASSETS_STATE_LOADING));
|
navigatorStateLabel.setText(Container.i18n().text(I18n.ASSETS_STATE_LOADING));
|
||||||
navigatorContent.getChildren().setAll(createNavigatorMessage(Container.i18n().text(I18n.ASSETS_STATE_LOADING)));
|
navigatorContent.getChildren().setAll(createNavigatorMessage(Container.i18n().text(I18n.ASSETS_STATE_LOADING)));
|
||||||
workspaceSummaryLabel.setText(Container.i18n().text(I18n.ASSETS_SUMMARY_LOADING));
|
|
||||||
detailStateLabel.setText(Container.i18n().text(I18n.ASSETS_DETAILS_LOADING));
|
|
||||||
}
|
}
|
||||||
case EMPTY -> {
|
case EMPTY -> {
|
||||||
navigatorStateLabel.setText(Container.i18n().text(I18n.ASSETS_STATE_EMPTY));
|
navigatorStateLabel.setText(Container.i18n().text(I18n.ASSETS_STATE_EMPTY));
|
||||||
navigatorContent.getChildren().setAll(createNavigatorMessage(Container.i18n().text(I18n.ASSETS_STATE_EMPTY)));
|
navigatorContent.getChildren().setAll(createNavigatorMessage(Container.i18n().text(I18n.ASSETS_STATE_EMPTY)));
|
||||||
workspaceSummaryLabel.setText(Container.i18n().text(I18n.ASSETS_SUMMARY_EMPTY));
|
|
||||||
detailStateLabel.setText(Container.i18n().text(I18n.ASSETS_DETAILS_EMPTY));
|
|
||||||
}
|
}
|
||||||
case ERROR -> {
|
case ERROR -> {
|
||||||
navigatorStateLabel.setText(Container.i18n().text(I18n.ASSETS_STATE_ERROR) + "\n\n" + state.errorMessage());
|
navigatorStateLabel.setText(Container.i18n().text(I18n.ASSETS_STATE_ERROR) + "\n\n" + state.errorMessage());
|
||||||
navigatorContent.getChildren().setAll(createNavigatorMessage(state.errorMessage()));
|
navigatorContent.getChildren().setAll(createNavigatorMessage(state.errorMessage()));
|
||||||
workspaceSummaryLabel.setText(Container.i18n().text(I18n.ASSETS_SUMMARY_ERROR));
|
|
||||||
detailStateLabel.setText(state.errorMessage());
|
|
||||||
}
|
}
|
||||||
case READY -> {
|
case READY -> {
|
||||||
final int assetCount = state.assets().size();
|
|
||||||
final AssetNavigatorProjection projection = AssetNavigatorProjectionBuilder.build(
|
final AssetNavigatorProjection projection = AssetNavigatorProjectionBuilder.build(
|
||||||
state.assets(),
|
state.assets(),
|
||||||
assetsRoot(),
|
assetsRoot(),
|
||||||
@ -197,22 +251,297 @@ public final class AssetWorkspace implements Workspace {
|
|||||||
navigatorStateLabel.setText(Container.i18n().text(I18n.ASSETS_STATE_NO_RESULTS));
|
navigatorStateLabel.setText(Container.i18n().text(I18n.ASSETS_STATE_NO_RESULTS));
|
||||||
navigatorContent.getChildren().setAll(createNavigatorMessage(Container.i18n().text(I18n.ASSETS_STATE_NO_RESULTS)));
|
navigatorContent.getChildren().setAll(createNavigatorMessage(Container.i18n().text(I18n.ASSETS_STATE_NO_RESULTS)));
|
||||||
} else {
|
} else {
|
||||||
navigatorStateLabel.setText(Container.i18n().format(I18n.ASSETS_STATE_READY, projection.visibleAssetCount(), assetCount));
|
navigatorStateLabel.setText(Container.i18n().format(I18n.ASSETS_STATE_READY, projection.visibleAssetCount(), state.assets().size()));
|
||||||
renderNavigatorProjection(projection);
|
renderNavigatorProjection(projection);
|
||||||
}
|
}
|
||||||
workspaceSummaryLabel.setText(Container.i18n().format(I18n.ASSETS_SUMMARY_READY, assetCount));
|
|
||||||
final String selectedDescription = state.selectedAsset()
|
|
||||||
.map(asset -> Container.i18n().format(
|
|
||||||
I18n.ASSETS_DETAILS_READY,
|
|
||||||
asset.assetName(),
|
|
||||||
asset.state().name().toLowerCase(),
|
|
||||||
asset.assetRoot()))
|
|
||||||
.orElseGet(() -> Container.i18n().text(I18n.ASSETS_DETAILS_NO_SELECTION));
|
|
||||||
detailStateLabel.setText(selectedDescription);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void renderDetails() {
|
||||||
|
detailsContent.getChildren().clear();
|
||||||
|
switch (state.status()) {
|
||||||
|
case LOADING -> {
|
||||||
|
workspaceSummaryLabel.setText(Container.i18n().text(I18n.ASSETS_SUMMARY_LOADING));
|
||||||
|
detailsContent.getChildren().add(createSectionMessage(Container.i18n().text(I18n.ASSETS_DETAILS_LOADING)));
|
||||||
|
}
|
||||||
|
case EMPTY -> {
|
||||||
|
workspaceSummaryLabel.setText(Container.i18n().text(I18n.ASSETS_SUMMARY_EMPTY));
|
||||||
|
detailsContent.getChildren().add(createSectionMessage(Container.i18n().text(I18n.ASSETS_DETAILS_EMPTY)));
|
||||||
|
}
|
||||||
|
case ERROR -> {
|
||||||
|
workspaceSummaryLabel.setText(Container.i18n().text(I18n.ASSETS_SUMMARY_ERROR));
|
||||||
|
detailsContent.getChildren().add(createSectionMessage(state.errorMessage()));
|
||||||
|
}
|
||||||
|
case READY -> {
|
||||||
|
workspaceSummaryLabel.setText(Container.i18n().format(I18n.ASSETS_SUMMARY_READY, state.assets().size()));
|
||||||
|
state.selectedAsset()
|
||||||
|
.ifPresentOrElse(this::renderSelectedAssetDetails, () ->
|
||||||
|
detailsContent.getChildren().add(createSectionMessage(Container.i18n().text(I18n.ASSETS_DETAILS_NO_SELECTION))));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void renderSelectedAssetDetails(AssetWorkspaceAssetSummary summary) {
|
||||||
|
detailsContent.getChildren().add(createSummarySection(summary));
|
||||||
|
if (detailsStatus == AssetWorkspaceDetailsStatus.LOADING) {
|
||||||
|
detailsContent.getChildren().add(createSection(
|
||||||
|
Container.i18n().text(I18n.ASSETS_SECTION_RUNTIME_CONTRACT),
|
||||||
|
createSectionMessage(Container.i18n().text(I18n.ASSETS_DETAILS_LOADING))));
|
||||||
|
detailsContent.getChildren().add(createSection(
|
||||||
|
Container.i18n().text(I18n.ASSETS_SECTION_INPUTS_PREVIEW),
|
||||||
|
createSectionMessage(Container.i18n().text(I18n.ASSETS_DETAILS_LOADING))));
|
||||||
|
detailsContent.getChildren().add(createSection(
|
||||||
|
Container.i18n().text(I18n.ASSETS_SECTION_DIAGNOSTICS),
|
||||||
|
createSectionMessage(Container.i18n().text(I18n.ASSETS_DETAILS_LOADING))));
|
||||||
|
detailsContent.getChildren().add(createSection(
|
||||||
|
Container.i18n().text(I18n.ASSETS_SECTION_ACTIONS),
|
||||||
|
createActionsContent(summary)));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (detailsStatus == AssetWorkspaceDetailsStatus.ERROR || selectedAssetDetails == null) {
|
||||||
|
detailsContent.getChildren().add(createSection(
|
||||||
|
Container.i18n().text(I18n.ASSETS_SECTION_RUNTIME_CONTRACT),
|
||||||
|
createSectionMessage(Objects.requireNonNullElse(detailsErrorMessage, Container.i18n().text(I18n.ASSETS_DETAILS_EMPTY)))));
|
||||||
|
detailsContent.getChildren().add(createSection(
|
||||||
|
Container.i18n().text(I18n.ASSETS_SECTION_INPUTS_PREVIEW),
|
||||||
|
createSectionMessage(Objects.requireNonNullElse(detailsErrorMessage, Container.i18n().text(I18n.ASSETS_DETAILS_EMPTY)))));
|
||||||
|
detailsContent.getChildren().add(createSection(
|
||||||
|
Container.i18n().text(I18n.ASSETS_SECTION_DIAGNOSTICS),
|
||||||
|
createSectionMessage(Objects.requireNonNullElse(detailsErrorMessage, Container.i18n().text(I18n.ASSETS_DETAILS_EMPTY)))));
|
||||||
|
detailsContent.getChildren().add(createSection(
|
||||||
|
Container.i18n().text(I18n.ASSETS_SECTION_ACTIONS),
|
||||||
|
createActionsContent(summary)));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
detailsContent.getChildren().add(createRuntimeContractSection(selectedAssetDetails));
|
||||||
|
detailsContent.getChildren().add(createInputsPreviewSection(selectedAssetDetails));
|
||||||
|
detailsContent.getChildren().add(createDiagnosticsSection(selectedAssetDetails));
|
||||||
|
detailsContent.getChildren().add(createSection(
|
||||||
|
Container.i18n().text(I18n.ASSETS_SECTION_ACTIONS),
|
||||||
|
createActionsContent(summary)));
|
||||||
|
}
|
||||||
|
|
||||||
|
private Node createSummarySection(AssetWorkspaceAssetSummary summary) {
|
||||||
|
final VBox content = new VBox(8);
|
||||||
|
content.getChildren().addAll(
|
||||||
|
createKeyValueRow(Container.i18n().text(I18n.ASSETS_LABEL_NAME), summary.assetName()),
|
||||||
|
createKeyValueRow(Container.i18n().text(I18n.ASSETS_LABEL_STATE), summary.state().name().toLowerCase()),
|
||||||
|
createKeyValueRow(Container.i18n().text(I18n.ASSETS_LABEL_ASSET_ID), summary.assetId() == null ? "—" : String.valueOf(summary.assetId())),
|
||||||
|
createKeyValueRow(Container.i18n().text(I18n.ASSETS_LABEL_TYPE), summary.assetFamily()),
|
||||||
|
createKeyValueRow(Container.i18n().text(I18n.ASSETS_LABEL_LOCATION), summary.assetRoot().toString()));
|
||||||
|
return createSection(Container.i18n().text(I18n.ASSETS_SECTION_SUMMARY), content);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Node createRuntimeContractSection(AssetWorkspaceAssetDetails details) {
|
||||||
|
final VBox content = new VBox(8);
|
||||||
|
content.getChildren().addAll(
|
||||||
|
createKeyValueRow(Container.i18n().text(I18n.ASSETS_LABEL_FORMAT), details.outputFormat()),
|
||||||
|
createKeyValueRow(Container.i18n().text(I18n.ASSETS_LABEL_CODEC), details.outputCodec()),
|
||||||
|
createKeyValueRow(Container.i18n().text(I18n.ASSETS_LABEL_PRELOAD), yesNo(details.summary().preload())));
|
||||||
|
return createSection(Container.i18n().text(I18n.ASSETS_SECTION_RUNTIME_CONTRACT), content);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Node createInputsPreviewSection(AssetWorkspaceAssetDetails details) {
|
||||||
|
if (details.inputsByRole().isEmpty()) {
|
||||||
|
return createSection(
|
||||||
|
Container.i18n().text(I18n.ASSETS_SECTION_INPUTS_PREVIEW),
|
||||||
|
createSectionMessage(Container.i18n().text(I18n.ASSETS_INPUTS_EMPTY)));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selectedPreviewInput == null || !containsInput(details, selectedPreviewInput)) {
|
||||||
|
selectedPreviewInput = firstPreviewInput(details);
|
||||||
|
}
|
||||||
|
|
||||||
|
final VBox inputsList = new VBox(8);
|
||||||
|
for (Map.Entry<String, List<Path>> entry : details.inputsByRole().entrySet()) {
|
||||||
|
final VBox roleBox = new VBox(6);
|
||||||
|
final Label roleLabel = new Label(entry.getKey());
|
||||||
|
roleLabel.getStyleClass().add("assets-details-role-label");
|
||||||
|
roleBox.getChildren().add(roleLabel);
|
||||||
|
for (Path input : entry.getValue()) {
|
||||||
|
final Button inputButton = new Button(input.getFileName().toString());
|
||||||
|
inputButton.getStyleClass().add("assets-details-input-button");
|
||||||
|
if (input.equals(selectedPreviewInput)) {
|
||||||
|
inputButton.getStyleClass().add("assets-details-input-button-selected");
|
||||||
|
}
|
||||||
|
inputButton.setMaxWidth(Double.MAX_VALUE);
|
||||||
|
inputButton.setOnAction(event -> {
|
||||||
|
selectedPreviewInput = input;
|
||||||
|
renderState();
|
||||||
|
});
|
||||||
|
roleBox.getChildren().add(inputButton);
|
||||||
|
}
|
||||||
|
inputsList.getChildren().add(roleBox);
|
||||||
|
}
|
||||||
|
|
||||||
|
final Node previewPane = createPreviewPane(selectedPreviewInput);
|
||||||
|
final SplitPane splitPane = new SplitPane(inputsList, previewPane);
|
||||||
|
splitPane.setOrientation(Orientation.HORIZONTAL);
|
||||||
|
splitPane.setDividerPositions(0.34);
|
||||||
|
splitPane.getStyleClass().add("assets-details-input-preview-split");
|
||||||
|
return createSection(Container.i18n().text(I18n.ASSETS_SECTION_INPUTS_PREVIEW), splitPane);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Node createDiagnosticsSection(AssetWorkspaceAssetDetails details) {
|
||||||
|
if (details.diagnostics().isEmpty()) {
|
||||||
|
return createSection(
|
||||||
|
Container.i18n().text(I18n.ASSETS_SECTION_DIAGNOSTICS),
|
||||||
|
createSectionMessage(Container.i18n().text(I18n.ASSETS_DIAGNOSTICS_EMPTY)));
|
||||||
|
}
|
||||||
|
|
||||||
|
final VBox diagnosticsBox = new VBox(8);
|
||||||
|
for (AssetWorkspaceDiagnostic diagnostic : details.diagnostics()) {
|
||||||
|
final VBox card = new VBox(4);
|
||||||
|
card.getStyleClass().add("assets-details-diagnostic-card");
|
||||||
|
card.getStyleClass().add("assets-details-diagnostic-" + diagnostic.severity().name().toLowerCase());
|
||||||
|
final Label severity = new Label(diagnostic.severity().name());
|
||||||
|
severity.getStyleClass().add("assets-details-diagnostic-severity");
|
||||||
|
final Label message = new Label(diagnostic.message());
|
||||||
|
message.getStyleClass().add("assets-details-diagnostic-message");
|
||||||
|
message.setWrapText(true);
|
||||||
|
card.getChildren().addAll(severity, message);
|
||||||
|
diagnosticsBox.getChildren().add(card);
|
||||||
|
}
|
||||||
|
return createSection(Container.i18n().text(I18n.ASSETS_SECTION_DIAGNOSTICS), diagnosticsBox);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Node createActionsContent(AssetWorkspaceAssetSummary summary) {
|
||||||
|
final AssetWorkspaceActionSet actionSet = AssetWorkspaceActionSetBuilder.forAsset(summary);
|
||||||
|
final VBox content = new VBox(12);
|
||||||
|
|
||||||
|
final VBox primaryBox = new VBox(8);
|
||||||
|
final Label primaryLabel = new Label(Container.i18n().text(I18n.ASSETS_ACTIONS_PRIMARY));
|
||||||
|
primaryLabel.getStyleClass().add("assets-details-subsection-title");
|
||||||
|
primaryBox.getChildren().add(primaryLabel);
|
||||||
|
final FlowPane primaryButtons = new FlowPane();
|
||||||
|
primaryButtons.setHgap(8);
|
||||||
|
primaryButtons.setVgap(8);
|
||||||
|
for (AssetWorkspaceAction action : actionSet.primaryActions()) {
|
||||||
|
primaryButtons.getChildren().add(createActionButton(action, false));
|
||||||
|
}
|
||||||
|
primaryBox.getChildren().add(primaryButtons);
|
||||||
|
content.getChildren().add(primaryBox);
|
||||||
|
|
||||||
|
if (!actionSet.sensitiveActions().isEmpty()) {
|
||||||
|
final VBox sensitiveBox = new VBox(8);
|
||||||
|
final Label sensitiveLabel = new Label(Container.i18n().text(I18n.ASSETS_ACTIONS_SENSITIVE));
|
||||||
|
sensitiveLabel.getStyleClass().add("assets-details-subsection-title");
|
||||||
|
sensitiveBox.getChildren().add(sensitiveLabel);
|
||||||
|
final FlowPane sensitiveButtons = new FlowPane();
|
||||||
|
sensitiveButtons.setHgap(8);
|
||||||
|
sensitiveButtons.setVgap(8);
|
||||||
|
for (AssetWorkspaceAction action : actionSet.sensitiveActions()) {
|
||||||
|
sensitiveButtons.getChildren().add(createActionButton(action, true));
|
||||||
|
}
|
||||||
|
sensitiveBox.getChildren().add(sensitiveButtons);
|
||||||
|
content.getChildren().add(sensitiveBox);
|
||||||
|
}
|
||||||
|
|
||||||
|
return content;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Button createActionButton(AssetWorkspaceAction action, boolean sensitive) {
|
||||||
|
final Button button = new Button(actionLabel(action));
|
||||||
|
button.getStyleClass().add("assets-details-action-button");
|
||||||
|
if (sensitive) {
|
||||||
|
button.getStyleClass().add("assets-details-action-button-sensitive");
|
||||||
|
} else {
|
||||||
|
button.getStyleClass().add("assets-details-action-button-primary");
|
||||||
|
}
|
||||||
|
button.setDisable(true);
|
||||||
|
return button;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String actionLabel(AssetWorkspaceAction action) {
|
||||||
|
return switch (action) {
|
||||||
|
case DOCTOR -> Container.i18n().text(I18n.ASSETS_ACTION_DOCTOR);
|
||||||
|
case BUILD -> Container.i18n().text(I18n.ASSETS_ACTION_BUILD);
|
||||||
|
case ADOPT -> Container.i18n().text(I18n.ASSETS_ACTION_ADOPT);
|
||||||
|
case REGISTER -> Container.i18n().text(I18n.ASSETS_ACTION_REGISTER);
|
||||||
|
case FORGET -> Container.i18n().text(I18n.ASSETS_ACTION_FORGET);
|
||||||
|
case REMOVE -> Container.i18n().text(I18n.ASSETS_ACTION_REMOVE);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private Node createPreviewPane(Path input) {
|
||||||
|
final VBox previewBox = new VBox(10);
|
||||||
|
previewBox.getStyleClass().add("assets-details-preview-pane");
|
||||||
|
if (input == null) {
|
||||||
|
previewBox.getChildren().add(createSectionMessage(Container.i18n().text(I18n.ASSETS_PREVIEW_EMPTY)));
|
||||||
|
return previewBox;
|
||||||
|
}
|
||||||
|
|
||||||
|
final Label title = new Label(input.getFileName().toString());
|
||||||
|
title.getStyleClass().add("assets-details-preview-title");
|
||||||
|
previewBox.getChildren().add(title);
|
||||||
|
|
||||||
|
final String extension = extensionOf(input);
|
||||||
|
if (isImage(extension)) {
|
||||||
|
try {
|
||||||
|
final Image image = new Image(input.toUri().toString(), true);
|
||||||
|
final ImageView imageView = new ImageView(image);
|
||||||
|
imageView.setPreserveRatio(true);
|
||||||
|
imageView.setFitWidth(420);
|
||||||
|
previewBox.getChildren().add(imageView);
|
||||||
|
} catch (RuntimeException runtimeException) {
|
||||||
|
previewBox.getChildren().add(createSectionMessage(Container.i18n().text(I18n.ASSETS_PREVIEW_IMAGE_ERROR)));
|
||||||
|
}
|
||||||
|
previewBox.getChildren().add(createKeyValueRow(Container.i18n().text(I18n.ASSETS_LABEL_LOCATION), input.toString()));
|
||||||
|
return previewBox;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isText(extension)) {
|
||||||
|
final TextArea textArea = new TextArea(readPreviewText(input));
|
||||||
|
textArea.setWrapText(true);
|
||||||
|
textArea.setEditable(false);
|
||||||
|
textArea.getStyleClass().add("assets-details-preview-text");
|
||||||
|
previewBox.getChildren().add(textArea);
|
||||||
|
return previewBox;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isAudio(extension)) {
|
||||||
|
previewBox.getChildren().add(createSectionMessage(Container.i18n().format(I18n.ASSETS_PREVIEW_AUDIO_PLACEHOLDER, input.getFileName().toString())));
|
||||||
|
previewBox.getChildren().add(createKeyValueRow(Container.i18n().text(I18n.ASSETS_LABEL_LOCATION), input.toString()));
|
||||||
|
return previewBox;
|
||||||
|
}
|
||||||
|
|
||||||
|
previewBox.getChildren().add(createSectionMessage(Container.i18n().format(I18n.ASSETS_PREVIEW_GENERIC_PLACEHOLDER, input.getFileName().toString())));
|
||||||
|
previewBox.getChildren().add(createKeyValueRow(Container.i18n().text(I18n.ASSETS_LABEL_LOCATION), input.toString()));
|
||||||
|
return previewBox;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Node createSection(String title, Node content) {
|
||||||
|
final VBox section = new VBox(10);
|
||||||
|
section.getStyleClass().add("assets-details-section");
|
||||||
|
final Label titleLabel = new Label(title);
|
||||||
|
titleLabel.getStyleClass().add("assets-details-section-title");
|
||||||
|
section.getChildren().addAll(titleLabel, content);
|
||||||
|
return section;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Node createSectionMessage(String text) {
|
||||||
|
final Label label = new Label(text);
|
||||||
|
label.setWrapText(true);
|
||||||
|
label.getStyleClass().add("assets-details-section-message");
|
||||||
|
return label;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Node createKeyValueRow(String key, String value) {
|
||||||
|
final HBox row = new HBox(12);
|
||||||
|
row.setAlignment(Pos.TOP_LEFT);
|
||||||
|
final Label keyLabel = new Label(key);
|
||||||
|
keyLabel.getStyleClass().add("assets-details-key");
|
||||||
|
final Label valueLabel = new Label(value);
|
||||||
|
valueLabel.getStyleClass().add("assets-details-value");
|
||||||
|
valueLabel.setWrapText(true);
|
||||||
|
HBox.setHgrow(valueLabel, Priority.ALWAYS);
|
||||||
|
row.getChildren().addAll(keyLabel, valueLabel);
|
||||||
|
return row;
|
||||||
|
}
|
||||||
|
|
||||||
private void renderNavigatorProjection(AssetNavigatorProjection projection) {
|
private void renderNavigatorProjection(AssetNavigatorProjection projection) {
|
||||||
navigatorContent.getChildren().clear();
|
navigatorContent.getChildren().clear();
|
||||||
for (AssetNavigatorGroup group : projection.groups()) {
|
for (AssetNavigatorGroup group : projection.groups()) {
|
||||||
@ -239,7 +568,7 @@ public final class AssetWorkspace implements Workspace {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final HBox topLine = new HBox(8);
|
final HBox topLine = new HBox(8);
|
||||||
topLine.setAlignment(javafx.geometry.Pos.CENTER_LEFT);
|
topLine.setAlignment(Pos.CENTER_LEFT);
|
||||||
final Label icon = new Label(assetIcon(asset));
|
final Label icon = new Label(assetIcon(asset));
|
||||||
icon.getStyleClass().add("assets-workspace-asset-icon");
|
icon.getStyleClass().add("assets-workspace-asset-icon");
|
||||||
final Label name = new Label(asset.assetName());
|
final Label name = new Label(asset.assetName());
|
||||||
@ -290,6 +619,65 @@ public final class AssetWorkspace implements Workspace {
|
|||||||
state = state.withSelection(selectionKey);
|
state = state.withSelection(selectionKey);
|
||||||
renderState();
|
renderState();
|
||||||
workspaceBus.publish(new StudioAssetsWorkspaceSelectionChangedEvent(projectReference, selectionKey));
|
workspaceBus.publish(new StudioAssetsWorkspaceSelectionChangedEvent(projectReference, selectionKey));
|
||||||
|
loadSelectedAssetDetails(selectionKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Path firstPreviewInput(AssetWorkspaceAssetDetails details) {
|
||||||
|
return details.inputsByRole().values().stream()
|
||||||
|
.flatMap(List::stream)
|
||||||
|
.findFirst()
|
||||||
|
.orElse(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean containsInput(AssetWorkspaceAssetDetails details, Path input) {
|
||||||
|
return details.inputsByRole().values().stream().flatMap(List::stream).anyMatch(input::equals);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String readPreviewText(Path input) {
|
||||||
|
try {
|
||||||
|
final String text = Files.readString(input);
|
||||||
|
return text.length() > 4000 ? text.substring(0, 4000) + "\n\n…" : text;
|
||||||
|
} catch (IOException ioException) {
|
||||||
|
return Container.i18n().text(I18n.ASSETS_PREVIEW_TEXT_ERROR);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String extensionOf(Path input) {
|
||||||
|
final String fileName = input.getFileName().toString();
|
||||||
|
final int dot = fileName.lastIndexOf('.');
|
||||||
|
if (dot < 0 || dot == fileName.length() - 1) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
return fileName.substring(dot + 1).toLowerCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isImage(String extension) {
|
||||||
|
return extension.equals("png")
|
||||||
|
|| extension.equals("jpg")
|
||||||
|
|| extension.equals("jpeg")
|
||||||
|
|| extension.equals("gif")
|
||||||
|
|| extension.equals("bmp");
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isText(String extension) {
|
||||||
|
return extension.equals("json")
|
||||||
|
|| extension.equals("txt")
|
||||||
|
|| extension.equals("pal")
|
||||||
|
|| extension.equals("csv")
|
||||||
|
|| extension.equals("yaml")
|
||||||
|
|| extension.equals("yml")
|
||||||
|
|| extension.equals("xml")
|
||||||
|
|| extension.equals("pbs");
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isAudio(String extension) {
|
||||||
|
return extension.equals("wav")
|
||||||
|
|| extension.equals("ogg")
|
||||||
|
|| extension.equals("mp3");
|
||||||
|
}
|
||||||
|
|
||||||
|
private String yesNo(boolean value) {
|
||||||
|
return value ? Container.i18n().text(I18n.ASSETS_VALUE_YES) : Container.i18n().text(I18n.ASSETS_VALUE_NO);
|
||||||
}
|
}
|
||||||
|
|
||||||
private String assetIcon(AssetWorkspaceAssetSummary asset) {
|
private String assetIcon(AssetWorkspaceAssetSummary asset) {
|
||||||
|
|||||||
@ -0,0 +1,10 @@
|
|||||||
|
package p.studio.workspaces.assets;
|
||||||
|
|
||||||
|
public enum AssetWorkspaceAction {
|
||||||
|
DOCTOR,
|
||||||
|
BUILD,
|
||||||
|
ADOPT,
|
||||||
|
REGISTER,
|
||||||
|
FORGET,
|
||||||
|
REMOVE
|
||||||
|
}
|
||||||
@ -0,0 +1,13 @@
|
|||||||
|
package p.studio.workspaces.assets;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
public record AssetWorkspaceActionSet(
|
||||||
|
List<AssetWorkspaceAction> primaryActions,
|
||||||
|
List<AssetWorkspaceAction> sensitiveActions) {
|
||||||
|
public AssetWorkspaceActionSet {
|
||||||
|
primaryActions = List.copyOf(Objects.requireNonNull(primaryActions, "primaryActions"));
|
||||||
|
sensitiveActions = List.copyOf(Objects.requireNonNull(sensitiveActions, "sensitiveActions"));
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,21 @@
|
|||||||
|
package p.studio.workspaces.assets;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
public final class AssetWorkspaceActionSetBuilder {
|
||||||
|
private AssetWorkspaceActionSetBuilder() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static AssetWorkspaceActionSet forAsset(AssetWorkspaceAssetSummary summary) {
|
||||||
|
Objects.requireNonNull(summary, "summary");
|
||||||
|
return switch (summary.state()) {
|
||||||
|
case MANAGED -> new AssetWorkspaceActionSet(
|
||||||
|
List.of(AssetWorkspaceAction.DOCTOR, AssetWorkspaceAction.BUILD),
|
||||||
|
List.of(AssetWorkspaceAction.FORGET, AssetWorkspaceAction.REMOVE));
|
||||||
|
case ORPHAN -> new AssetWorkspaceActionSet(
|
||||||
|
List.of(AssetWorkspaceAction.ADOPT, AssetWorkspaceAction.REGISTER),
|
||||||
|
List.of());
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,22 @@
|
|||||||
|
package p.studio.workspaces.assets;
|
||||||
|
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
public record AssetWorkspaceAssetDetails(
|
||||||
|
AssetWorkspaceAssetSummary summary,
|
||||||
|
String outputFormat,
|
||||||
|
String outputCodec,
|
||||||
|
Map<String, List<Path>> inputsByRole,
|
||||||
|
List<AssetWorkspaceDiagnostic> diagnostics) {
|
||||||
|
|
||||||
|
public AssetWorkspaceAssetDetails {
|
||||||
|
Objects.requireNonNull(summary, "summary");
|
||||||
|
outputFormat = Objects.requireNonNullElse(outputFormat, "unknown");
|
||||||
|
outputCodec = Objects.requireNonNullElse(outputCodec, "unknown");
|
||||||
|
inputsByRole = Map.copyOf(Objects.requireNonNull(inputsByRole, "inputsByRole"));
|
||||||
|
diagnostics = List.copyOf(Objects.requireNonNull(diagnostics, "diagnostics"));
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,8 @@
|
|||||||
|
package p.studio.workspaces.assets;
|
||||||
|
|
||||||
|
public enum AssetWorkspaceDetailsStatus {
|
||||||
|
EMPTY,
|
||||||
|
LOADING,
|
||||||
|
READY,
|
||||||
|
ERROR
|
||||||
|
}
|
||||||
@ -0,0 +1,16 @@
|
|||||||
|
package p.studio.workspaces.assets;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
public record AssetWorkspaceDiagnostic(
|
||||||
|
AssetWorkspaceDiagnosticSeverity severity,
|
||||||
|
String message) {
|
||||||
|
|
||||||
|
public AssetWorkspaceDiagnostic {
|
||||||
|
Objects.requireNonNull(severity, "severity");
|
||||||
|
message = Objects.requireNonNull(message, "message").trim();
|
||||||
|
if (message.isBlank()) {
|
||||||
|
throw new IllegalArgumentException("message must not be blank");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,7 @@
|
|||||||
|
package p.studio.workspaces.assets;
|
||||||
|
|
||||||
|
public enum AssetWorkspaceDiagnosticSeverity {
|
||||||
|
BLOCKER,
|
||||||
|
WARNING,
|
||||||
|
HINT
|
||||||
|
}
|
||||||
@ -4,4 +4,6 @@ import p.studio.projects.ProjectReference;
|
|||||||
|
|
||||||
public interface AssetWorkspaceService {
|
public interface AssetWorkspaceService {
|
||||||
AssetWorkspaceSnapshot loadWorkspace(ProjectReference projectReference);
|
AssetWorkspaceSnapshot loadWorkspace(ProjectReference projectReference);
|
||||||
|
|
||||||
|
AssetWorkspaceAssetDetails loadAssetDetails(ProjectReference projectReference, AssetWorkspaceSelectionKey selectionKey);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
package p.studio.workspaces.assets;
|
package p.studio.workspaces.assets;
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||||
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import p.studio.projects.ProjectReference;
|
import p.studio.projects.ProjectReference;
|
||||||
|
|
||||||
@ -37,6 +38,33 @@ public final class FileSystemAssetWorkspaceService implements AssetWorkspaceServ
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AssetWorkspaceAssetDetails loadAssetDetails(ProjectReference projectReference, AssetWorkspaceSelectionKey selectionKey) {
|
||||||
|
final Path projectRoot = Objects.requireNonNull(projectReference, "projectReference").rootPath();
|
||||||
|
final Path assetsRoot = projectRoot.resolve("assets");
|
||||||
|
final Map<Path, Integer> registryByRoot = readRegistry(assetsRoot);
|
||||||
|
final Path assetRoot = resolveAssetRoot(selectionKey, assetsRoot, registryByRoot);
|
||||||
|
final Path assetManifestPath = assetRoot.resolve("asset.json");
|
||||||
|
try {
|
||||||
|
final JsonNode root = MAPPER.readTree(assetManifestPath.toFile());
|
||||||
|
final AssetWorkspaceAssetSummary summary = buildAssetSummary(assetManifestPath, registryByRoot);
|
||||||
|
final String outputFormat = readText(root.path("output").path("format"), "unknown");
|
||||||
|
final String outputCodec = readText(root.path("output").path("codec"), "unknown");
|
||||||
|
final Map<String, List<Path>> inputsByRole = readInputs(root.path("inputs"), assetRoot);
|
||||||
|
return new AssetWorkspaceAssetDetails(summary, outputFormat, outputCodec, inputsByRole, List.of());
|
||||||
|
} catch (IOException ioException) {
|
||||||
|
final AssetWorkspaceAssetSummary summary = buildAssetSummary(assetManifestPath, registryByRoot);
|
||||||
|
return new AssetWorkspaceAssetDetails(
|
||||||
|
summary,
|
||||||
|
"unknown",
|
||||||
|
"unknown",
|
||||||
|
Map.of(),
|
||||||
|
List.of(new AssetWorkspaceDiagnostic(
|
||||||
|
AssetWorkspaceDiagnosticSeverity.BLOCKER,
|
||||||
|
"Unable to read asset.json for the selected asset.")));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private AssetWorkspaceAssetSummary buildAssetSummary(Path assetManifestPath, Map<Path, Integer> registryByRoot) {
|
private AssetWorkspaceAssetSummary buildAssetSummary(Path assetManifestPath, Map<Path, Integer> registryByRoot) {
|
||||||
final Path assetRoot = assetManifestPath.getParent().toAbsolutePath().normalize();
|
final Path assetRoot = assetManifestPath.getParent().toAbsolutePath().normalize();
|
||||||
try {
|
try {
|
||||||
@ -102,6 +130,51 @@ public final class FileSystemAssetWorkspaceService implements AssetWorkspaceServ
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Path resolveAssetRoot(
|
||||||
|
AssetWorkspaceSelectionKey selectionKey,
|
||||||
|
Path assetsRoot,
|
||||||
|
Map<Path, Integer> registryByRoot) {
|
||||||
|
return switch (selectionKey) {
|
||||||
|
case AssetWorkspaceSelectionKey.ManagedAsset managedAsset -> registryByRoot.entrySet().stream()
|
||||||
|
.filter(entry -> managedAsset.assetId() == entry.getValue())
|
||||||
|
.map(Map.Entry::getKey)
|
||||||
|
.findFirst()
|
||||||
|
.orElseThrow(() -> new IllegalArgumentException("managed asset root not found for assetId " + managedAsset.assetId()));
|
||||||
|
case AssetWorkspaceSelectionKey.OrphanAsset orphanAsset -> orphanAsset.assetRoot();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<String, List<Path>> readInputs(JsonNode inputsNode, Path assetRoot) {
|
||||||
|
if (inputsNode == null || !inputsNode.isObject()) {
|
||||||
|
return Map.of();
|
||||||
|
}
|
||||||
|
final Map<String, List<Path>> result = new LinkedHashMap<>();
|
||||||
|
final Iterator<Map.Entry<String, JsonNode>> fields = inputsNode.fields();
|
||||||
|
while (fields.hasNext()) {
|
||||||
|
final Map.Entry<String, JsonNode> entry = fields.next();
|
||||||
|
if (!entry.getValue().isArray()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
final List<Path> resolvedInputs = new ArrayList<>();
|
||||||
|
for (JsonNode inputNode : entry.getValue()) {
|
||||||
|
if (!inputNode.isTextual()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
resolvedInputs.add(assetRoot.resolve(inputNode.asText()).toAbsolutePath().normalize());
|
||||||
|
}
|
||||||
|
result.put(entry.getKey(), List.copyOf(resolvedInputs));
|
||||||
|
}
|
||||||
|
return Map.copyOf(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String readText(JsonNode node, String fallback) {
|
||||||
|
if (node == null || !node.isTextual()) {
|
||||||
|
return fallback;
|
||||||
|
}
|
||||||
|
final String text = node.asText();
|
||||||
|
return text == null || text.isBlank() ? fallback : text;
|
||||||
|
}
|
||||||
|
|
||||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||||
private record AssetManifest(String name, String type, Preload preload) {
|
private record AssetManifest(String name, String type, Preload preload) {
|
||||||
}
|
}
|
||||||
|
|||||||
@ -84,6 +84,36 @@ assets.badge.managed=Managed
|
|||||||
assets.badge.orphan=Orphan
|
assets.badge.orphan=Orphan
|
||||||
assets.badge.preload=Preload
|
assets.badge.preload=Preload
|
||||||
assets.badge.diagnostics=Diagnostics
|
assets.badge.diagnostics=Diagnostics
|
||||||
|
assets.section.summary=Summary
|
||||||
|
assets.section.runtimeContract=Runtime Contract
|
||||||
|
assets.section.inputsPreview=Inputs / Preview
|
||||||
|
assets.section.diagnostics=Diagnostics
|
||||||
|
assets.section.actions=Actions
|
||||||
|
assets.actions.primary=Primary Actions
|
||||||
|
assets.actions.sensitive=Sensitive Actions
|
||||||
|
assets.action.doctor=Doctor
|
||||||
|
assets.action.build=Build
|
||||||
|
assets.action.adopt=Adopt
|
||||||
|
assets.action.register=Register
|
||||||
|
assets.action.forget=Forget
|
||||||
|
assets.action.remove=Remove
|
||||||
|
assets.label.name=Name
|
||||||
|
assets.label.state=State
|
||||||
|
assets.label.assetId=Asset ID
|
||||||
|
assets.label.type=Type
|
||||||
|
assets.label.location=Location
|
||||||
|
assets.label.format=Format
|
||||||
|
assets.label.codec=Codec
|
||||||
|
assets.label.preload=Preload
|
||||||
|
assets.value.yes=Yes
|
||||||
|
assets.value.no=No
|
||||||
|
assets.inputs.empty=No previewable inputs are currently declared for 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.textError=Unable to read this text-like input for preview.
|
||||||
|
assets.preview.imageError=Unable to decode this image for preview.
|
||||||
|
assets.preview.audioPlaceholder=Audio preview placeholder: {0}
|
||||||
|
assets.preview.genericPlaceholder=Preview placeholder for {0}
|
||||||
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.
|
||||||
|
|||||||
@ -272,6 +272,151 @@
|
|||||||
-fx-padding: 10 0 0 0;
|
-fx-padding: 10 0 0 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.assets-workspace-details-scroll {
|
||||||
|
-fx-background-color: transparent;
|
||||||
|
-fx-fit-to-width: true;
|
||||||
|
}
|
||||||
|
|
||||||
|
.assets-workspace-details-scroll > .viewport {
|
||||||
|
-fx-background-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.assets-workspace-details-content {
|
||||||
|
-fx-padding: 4 0 4 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.assets-details-section {
|
||||||
|
-fx-background-color: #11151b;
|
||||||
|
-fx-background-radius: 12;
|
||||||
|
-fx-border-radius: 12;
|
||||||
|
-fx-border-color: #26313d;
|
||||||
|
-fx-padding: 12;
|
||||||
|
}
|
||||||
|
|
||||||
|
.assets-details-section-title {
|
||||||
|
-fx-text-fill: #f5f9ff;
|
||||||
|
-fx-font-size: 14px;
|
||||||
|
-fx-font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.assets-details-subsection-title {
|
||||||
|
-fx-text-fill: #b8d7f6;
|
||||||
|
-fx-font-size: 12px;
|
||||||
|
-fx-font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.assets-details-section-message {
|
||||||
|
-fx-text-fill: #cbd4de;
|
||||||
|
-fx-font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.assets-details-key {
|
||||||
|
-fx-text-fill: #8fa5bc;
|
||||||
|
-fx-font-size: 11px;
|
||||||
|
-fx-min-width: 88;
|
||||||
|
-fx-font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.assets-details-value {
|
||||||
|
-fx-text-fill: #eef4fb;
|
||||||
|
-fx-font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.assets-details-role-label {
|
||||||
|
-fx-text-fill: #9fc3e7;
|
||||||
|
-fx-font-size: 11px;
|
||||||
|
-fx-font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.assets-details-input-button {
|
||||||
|
-fx-background-color: #17202a;
|
||||||
|
-fx-text-fill: #e6eff8;
|
||||||
|
-fx-background-radius: 8;
|
||||||
|
-fx-border-radius: 8;
|
||||||
|
-fx-border-color: #2f4053;
|
||||||
|
-fx-alignment: center-left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.assets-details-input-button-selected {
|
||||||
|
-fx-background-color: #224160;
|
||||||
|
-fx-border-color: #4f8dc3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.assets-details-input-preview-split {
|
||||||
|
-fx-background-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.assets-details-preview-pane {
|
||||||
|
-fx-background-color: #0f1318;
|
||||||
|
-fx-background-radius: 10;
|
||||||
|
-fx-padding: 10;
|
||||||
|
-fx-spacing: 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
.assets-details-preview-title {
|
||||||
|
-fx-text-fill: #f2f8ff;
|
||||||
|
-fx-font-size: 13px;
|
||||||
|
-fx-font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.assets-details-preview-text {
|
||||||
|
-fx-control-inner-background: #10161d;
|
||||||
|
-fx-text-fill: #e4edf6;
|
||||||
|
-fx-highlight-fill: #2c5e91;
|
||||||
|
}
|
||||||
|
|
||||||
|
.assets-details-diagnostic-card {
|
||||||
|
-fx-background-radius: 10;
|
||||||
|
-fx-border-radius: 10;
|
||||||
|
-fx-border-width: 1;
|
||||||
|
-fx-padding: 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
.assets-details-diagnostic-blocker {
|
||||||
|
-fx-background-color: #3d181b;
|
||||||
|
-fx-border-color: #c15b64;
|
||||||
|
}
|
||||||
|
|
||||||
|
.assets-details-diagnostic-warning {
|
||||||
|
-fx-background-color: #3e3117;
|
||||||
|
-fx-border-color: #c89b43;
|
||||||
|
}
|
||||||
|
|
||||||
|
.assets-details-diagnostic-hint {
|
||||||
|
-fx-background-color: #173241;
|
||||||
|
-fx-border-color: #5f99c5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.assets-details-diagnostic-severity {
|
||||||
|
-fx-text-fill: #ffffff;
|
||||||
|
-fx-font-size: 11px;
|
||||||
|
-fx-font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.assets-details-diagnostic-message {
|
||||||
|
-fx-text-fill: #eef4fb;
|
||||||
|
-fx-font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.assets-details-action-button {
|
||||||
|
-fx-background-radius: 10;
|
||||||
|
-fx-border-radius: 10;
|
||||||
|
-fx-border-width: 1;
|
||||||
|
-fx-padding: 8 12 8 12;
|
||||||
|
}
|
||||||
|
|
||||||
|
.assets-details-action-button-primary {
|
||||||
|
-fx-background-color: #173322;
|
||||||
|
-fx-border-color: #2f8f59;
|
||||||
|
-fx-text-fill: #c7f8d7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.assets-details-action-button-sensitive {
|
||||||
|
-fx-background-color: #3b191c;
|
||||||
|
-fx-border-color: #c7606a;
|
||||||
|
-fx-text-fill: #ffd6d9;
|
||||||
|
}
|
||||||
|
|
||||||
.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,42 @@
|
|||||||
|
package p.studio.workspaces.assets;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
|
||||||
|
final class AssetWorkspaceActionSetBuilderTest {
|
||||||
|
@Test
|
||||||
|
void managedAssetsExposeDoctorBuildAndSensitiveMutations() {
|
||||||
|
final AssetWorkspaceActionSet actionSet = AssetWorkspaceActionSetBuilder.forAsset(new AssetWorkspaceAssetSummary(
|
||||||
|
new AssetWorkspaceSelectionKey.ManagedAsset(42),
|
||||||
|
"ui_atlas",
|
||||||
|
AssetWorkspaceAssetState.MANAGED,
|
||||||
|
42,
|
||||||
|
"image_bank",
|
||||||
|
Path.of("/tmp/assets/ui_atlas"),
|
||||||
|
true,
|
||||||
|
false));
|
||||||
|
|
||||||
|
assertEquals(List.of(AssetWorkspaceAction.DOCTOR, AssetWorkspaceAction.BUILD), actionSet.primaryActions());
|
||||||
|
assertEquals(List.of(AssetWorkspaceAction.FORGET, AssetWorkspaceAction.REMOVE), actionSet.sensitiveActions());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void orphanAssetsExposeAdoptAndRegisterOnly() {
|
||||||
|
final AssetWorkspaceActionSet actionSet = AssetWorkspaceActionSetBuilder.forAsset(new AssetWorkspaceAssetSummary(
|
||||||
|
new AssetWorkspaceSelectionKey.OrphanAsset(Path.of("/tmp/assets/ui_sounds")),
|
||||||
|
"ui_sounds",
|
||||||
|
AssetWorkspaceAssetState.ORPHAN,
|
||||||
|
null,
|
||||||
|
"sound_bank",
|
||||||
|
Path.of("/tmp/assets/ui_sounds"),
|
||||||
|
false,
|
||||||
|
false));
|
||||||
|
|
||||||
|
assertEquals(List.of(AssetWorkspaceAction.ADOPT, AssetWorkspaceAction.REGISTER), actionSet.primaryActions());
|
||||||
|
assertEquals(List.of(), actionSet.sensitiveActions());
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -6,6 +6,7 @@ import p.studio.projects.ProjectReference;
|
|||||||
|
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
@ -82,6 +83,57 @@ final class FileSystemAssetWorkspaceServiceTest {
|
|||||||
assertEquals(null, orphan.assetId());
|
assertEquals(null, orphan.assetId());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void loadsSelectedAssetDetailsIncludingContractAndInputs() throws Exception {
|
||||||
|
final Path projectRoot = tempDir.resolve("main");
|
||||||
|
final Path assetsRoot = projectRoot.resolve("assets");
|
||||||
|
final Path managedRoot = assetsRoot.resolve("ui").resolve("atlas");
|
||||||
|
Files.createDirectories(managedRoot.resolve("sprites"));
|
||||||
|
Files.createDirectories(managedRoot.resolve("palettes"));
|
||||||
|
Files.createDirectories(assetsRoot.resolve(".prometeu"));
|
||||||
|
Files.writeString(managedRoot.resolve("sprites").resolve("confirm.png"), "fake-image");
|
||||||
|
Files.writeString(managedRoot.resolve("palettes").resolve("ui_main.pal"), "00 11 22");
|
||||||
|
Files.writeString(managedRoot.resolve("asset.json"), """
|
||||||
|
{
|
||||||
|
"name": "ui_atlas",
|
||||||
|
"type": "image_bank",
|
||||||
|
"inputs": {
|
||||||
|
"sprites": ["sprites/confirm.png"],
|
||||||
|
"palettes": ["palettes/ui_main.pal"]
|
||||||
|
},
|
||||||
|
"output": {
|
||||||
|
"format": "TILES/indexed_v1",
|
||||||
|
"codec": "RAW"
|
||||||
|
},
|
||||||
|
"preload": { "enabled": true }
|
||||||
|
}
|
||||||
|
""");
|
||||||
|
Files.writeString(assetsRoot.resolve(".prometeu").resolve("index.json"), """
|
||||||
|
{
|
||||||
|
"schema_version": 1,
|
||||||
|
"next_asset_id": 2,
|
||||||
|
"assets": [
|
||||||
|
{
|
||||||
|
"asset_id": 1,
|
||||||
|
"root": "ui/atlas"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
""");
|
||||||
|
|
||||||
|
final FileSystemAssetWorkspaceService service = new FileSystemAssetWorkspaceService();
|
||||||
|
|
||||||
|
final AssetWorkspaceAssetDetails details = service.loadAssetDetails(
|
||||||
|
project("Main", projectRoot),
|
||||||
|
new AssetWorkspaceSelectionKey.ManagedAsset(1));
|
||||||
|
|
||||||
|
assertEquals("ui_atlas", details.summary().assetName());
|
||||||
|
assertEquals("TILES/indexed_v1", details.outputFormat());
|
||||||
|
assertEquals("RAW", details.outputCodec());
|
||||||
|
assertEquals(List.of(managedRoot.resolve("sprites/confirm.png").toAbsolutePath().normalize()), details.inputsByRole().get("sprites"));
|
||||||
|
assertEquals(List.of(managedRoot.resolve("palettes/ui_main.pal").toAbsolutePath().normalize()), details.inputsByRole().get("palettes"));
|
||||||
|
}
|
||||||
|
|
||||||
private ProjectReference project(String name, Path root) {
|
private ProjectReference project(String name, Path root) {
|
||||||
return new ProjectReference(name, "1.0.0", "pbs", 1, root);
|
return new ProjectReference(name, "1.0.0", "pbs", 1, root);
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user