implements PR-05b asset navigator search filters and selection

This commit is contained in:
bQUARKz 2026-03-11 16:43:51 +00:00
parent 75dc37d7e7
commit 17064aa505
Signed by: bquarkz
SSH Key Fingerprint: SHA256:Z7dgqoglWwoK6j6u4QC87OveEq74WOhFN+gitsxtkf8
9 changed files with 575 additions and 8 deletions

View File

@ -80,10 +80,20 @@ public enum I18n {
WORKSPACE_ASSETS("workspace.assets"),
ASSETS_NAVIGATOR_TITLE("assets.navigator.title"),
ASSETS_DETAILS_TITLE("assets.details.title"),
ASSETS_SEARCH_PROMPT("assets.search.prompt"),
ASSETS_FILTER_MANAGED("assets.filter.managed"),
ASSETS_FILTER_ORPHAN("assets.filter.orphan"),
ASSETS_FILTER_DIAGNOSTICS("assets.filter.diagnostics"),
ASSETS_FILTER_PRELOAD("assets.filter.preload"),
ASSETS_STATE_LOADING("assets.state.loading"),
ASSETS_STATE_EMPTY("assets.state.empty"),
ASSETS_STATE_NO_RESULTS("assets.state.noResults"),
ASSETS_STATE_READY("assets.state.ready"),
ASSETS_STATE_ERROR("assets.state.error"),
ASSETS_BADGE_MANAGED("assets.badge.managed"),
ASSETS_BADGE_ORPHAN("assets.badge.orphan"),
ASSETS_BADGE_PRELOAD("assets.badge.preload"),
ASSETS_BADGE_DIAGNOSTICS("assets.badge.diagnostics"),
ASSETS_SUMMARY_LOADING("assets.summary.loading"),
ASSETS_SUMMARY_EMPTY("assets.summary.empty"),
ASSETS_SUMMARY_READY("assets.summary.ready"),

View File

@ -0,0 +1,8 @@
package p.studio.workspaces.assets;
public enum AssetNavigatorFilter {
MANAGED,
ORPHAN,
DIAGNOSTICS,
PRELOAD
}

View File

@ -0,0 +1,14 @@
package p.studio.workspaces.assets;
import java.util.List;
import java.util.Objects;
public record AssetNavigatorGroup(String label, List<AssetWorkspaceAssetSummary> assets) {
public AssetNavigatorGroup {
label = Objects.requireNonNull(label, "label").trim();
assets = List.copyOf(Objects.requireNonNull(assets, "assets"));
if (label.isBlank()) {
throw new IllegalArgumentException("group label must not be blank");
}
}
}

View File

@ -0,0 +1,17 @@
package p.studio.workspaces.assets;
import java.util.List;
import java.util.Objects;
public record AssetNavigatorProjection(List<AssetNavigatorGroup> groups, int visibleAssetCount) {
public AssetNavigatorProjection {
groups = List.copyOf(Objects.requireNonNull(groups, "groups"));
if (visibleAssetCount < 0) {
throw new IllegalArgumentException("visibleAssetCount must not be negative");
}
}
public boolean isEmpty() {
return visibleAssetCount == 0;
}
}

View File

@ -0,0 +1,99 @@
package p.studio.workspaces.assets;
import java.nio.file.Path;
import java.util.*;
public final class AssetNavigatorProjectionBuilder {
private AssetNavigatorProjectionBuilder() {
}
public static AssetNavigatorProjection build(
List<AssetWorkspaceAssetSummary> assets,
Path assetsRoot,
String searchQuery,
Set<AssetNavigatorFilter> filters) {
Objects.requireNonNull(assets, "assets");
final Path normalizedAssetsRoot = Objects.requireNonNull(assetsRoot, "assetsRoot").toAbsolutePath().normalize();
final String normalizedQuery = normalizeQuery(searchQuery);
final Set<AssetNavigatorFilter> normalizedFilters = filters == null || filters.isEmpty()
? EnumSet.noneOf(AssetNavigatorFilter.class)
: EnumSet.copyOf(filters);
final Map<String, List<AssetWorkspaceAssetSummary>> grouped = new LinkedHashMap<>();
for (AssetWorkspaceAssetSummary asset : assets) {
if (!matchesFilters(asset, normalizedFilters) || !matchesQuery(asset, normalizedAssetsRoot, normalizedQuery)) {
continue;
}
grouped.computeIfAbsent(groupLabel(asset, normalizedAssetsRoot), ignored -> new ArrayList<>())
.add(asset);
}
final List<AssetNavigatorGroup> groups = grouped.entrySet().stream()
.map(entry -> new AssetNavigatorGroup(entry.getKey(), entry.getValue()))
.toList();
final int visibleAssetCount = groups.stream().mapToInt(group -> group.assets().size()).sum();
return new AssetNavigatorProjection(groups, visibleAssetCount);
}
static String relativeRoot(AssetWorkspaceAssetSummary asset, Path assetsRoot) {
return relativize(asset.assetRoot(), assetsRoot).toString().replace('\\', '/');
}
private static boolean matchesFilters(AssetWorkspaceAssetSummary asset, Set<AssetNavigatorFilter> filters) {
if (filters.isEmpty()) {
return true;
}
final boolean includeManaged = filters.contains(AssetNavigatorFilter.MANAGED);
final boolean includeOrphan = filters.contains(AssetNavigatorFilter.ORPHAN);
if (includeManaged || includeOrphan) {
final boolean stateMatches = (includeManaged && asset.state() == AssetWorkspaceAssetState.MANAGED)
|| (includeOrphan && asset.state() == AssetWorkspaceAssetState.ORPHAN);
if (!stateMatches) {
return false;
}
}
if (filters.contains(AssetNavigatorFilter.DIAGNOSTICS) && !asset.hasDiagnostics()) {
return false;
}
if (filters.contains(AssetNavigatorFilter.PRELOAD) && !asset.preload()) {
return false;
}
return true;
}
private static boolean matchesQuery(AssetWorkspaceAssetSummary asset, Path assetsRoot, String normalizedQuery) {
if (normalizedQuery.isBlank()) {
return true;
}
final String relativeRoot = relativeRoot(asset, assetsRoot);
return asset.assetName().toLowerCase(Locale.ROOT).contains(normalizedQuery)
|| asset.assetFamily().toLowerCase(Locale.ROOT).contains(normalizedQuery)
|| relativeRoot.toLowerCase(Locale.ROOT).contains(normalizedQuery);
}
private static String groupLabel(AssetWorkspaceAssetSummary asset, Path assetsRoot) {
final Path relativeRoot = relativize(asset.assetRoot(), assetsRoot);
final Path parent = relativeRoot.getParent();
if (parent == null) {
return "assets";
}
return parent.toString().replace('\\', '/');
}
private static Path relativize(Path assetRoot, Path assetsRoot) {
try {
return assetsRoot.relativize(assetRoot.toAbsolutePath().normalize());
} catch (IllegalArgumentException ignored) {
return assetRoot.getFileName();
}
}
private static String normalizeQuery(String query) {
return query == null ? "" : query.trim().toLowerCase(Locale.ROOT);
}
}

View File

@ -1,12 +1,10 @@
package p.studio.workspaces.assets;
import javafx.application.Platform;
import javafx.geometry.Insets;
import javafx.scene.Node;
import javafx.scene.control.Label;
import javafx.scene.control.SplitPane;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
import javafx.scene.control.*;
import javafx.scene.layout.*;
import p.studio.Container;
import p.studio.events.*;
import p.studio.projects.ProjectReference;
@ -14,17 +12,27 @@ import p.studio.utilities.i18n.I18n;
import p.studio.workspaces.Workspace;
import p.studio.workspaces.WorkspaceId;
import java.nio.file.Path;
import java.util.concurrent.CompletableFuture;
import java.util.EnumMap;
import java.util.EnumSet;
import java.util.Map;
public final class AssetWorkspace implements Workspace {
private final BorderPane root = new BorderPane();
private final ProjectReference projectReference;
private final AssetWorkspaceService assetWorkspaceService;
private final StudioWorkspaceEventBus workspaceBus;
private final TextField searchField = new TextField();
private final FlowPane filterBar = new FlowPane();
private final Label navigatorStateLabel = new Label();
private final VBox navigatorContent = new VBox(8);
private final Label detailStateLabel = new Label();
private final Label workspaceSummaryLabel = new Label();
private final Map<AssetNavigatorFilter, ToggleButton> filterButtons = new EnumMap<>(AssetNavigatorFilter.class);
private final EnumSet<AssetNavigatorFilter> activeFilters = EnumSet.noneOf(AssetNavigatorFilter.class);
private String searchQuery = "";
private volatile AssetWorkspaceState state = AssetWorkspaceState.loading(null);
public AssetWorkspace(ProjectReference projectReference) {
@ -70,9 +78,27 @@ public final class AssetWorkspace implements Workspace {
final Label navigatorTitle = new Label();
navigatorTitle.textProperty().bind(Container.i18n().bind(I18n.ASSETS_NAVIGATOR_TITLE));
navigatorTitle.getStyleClass().add("assets-workspace-pane-title");
searchField.setPromptText(Container.i18n().text(I18n.ASSETS_SEARCH_PROMPT));
searchField.getStyleClass().add("assets-workspace-search");
searchField.textProperty().addListener((ignored, oldValue, newValue) -> {
final String previous = oldValue == null ? "" : oldValue;
final String current = newValue == null ? "" : newValue;
if (previous.equals(current)) {
return;
}
searchQuery = current;
renderState();
});
configureFilterBar();
navigatorStateLabel.getStyleClass().add("assets-workspace-pane-body");
navigatorContent.getStyleClass().add("assets-workspace-navigator-content");
final ScrollPane navigatorScroll = new ScrollPane(navigatorContent);
navigatorScroll.setFitToWidth(true);
navigatorScroll.setHbarPolicy(ScrollPane.ScrollBarPolicy.NEVER);
navigatorScroll.getStyleClass().add("assets-workspace-navigator-scroll");
navigatorPane.getStyleClass().add("assets-workspace-pane");
navigatorPane.getChildren().addAll(navigatorTitle, navigatorStateLabel);
navigatorPane.getChildren().addAll(navigatorTitle, searchField, filterBar, navigatorStateLabel, navigatorScroll);
VBox.setVgrow(navigatorScroll, Priority.ALWAYS);
final VBox detailsPane = new VBox(8);
final Label detailsTitle = new Label();
@ -90,6 +116,33 @@ public final class AssetWorkspace implements Workspace {
return splitPane;
}
private void configureFilterBar() {
filterBar.setHgap(6);
filterBar.setVgap(6);
filterBar.setPadding(new Insets(4, 0, 4, 0));
filterBar.getStyleClass().add("assets-workspace-filter-bar");
addFilterButton(AssetNavigatorFilter.MANAGED, I18n.ASSETS_FILTER_MANAGED);
addFilterButton(AssetNavigatorFilter.ORPHAN, I18n.ASSETS_FILTER_ORPHAN);
addFilterButton(AssetNavigatorFilter.DIAGNOSTICS, I18n.ASSETS_FILTER_DIAGNOSTICS);
addFilterButton(AssetNavigatorFilter.PRELOAD, I18n.ASSETS_FILTER_PRELOAD);
}
private void addFilterButton(AssetNavigatorFilter filter, I18n i18n) {
final ToggleButton button = new ToggleButton();
button.textProperty().bind(Container.i18n().bind(i18n));
button.getStyleClass().add("assets-workspace-filter-button");
button.selectedProperty().addListener((ignored, oldValue, selected) -> {
if (selected) {
activeFilters.add(filter);
} else {
activeFilters.remove(filter);
}
renderState();
});
filterButtons.put(filter, button);
filterBar.getChildren().add(button);
}
private void refresh() {
state = AssetWorkspaceState.loading(state);
renderState();
@ -117,22 +170,36 @@ public final class AssetWorkspace implements Workspace {
switch (state.status()) {
case LOADING -> {
navigatorStateLabel.setText(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 -> {
navigatorStateLabel.setText(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 -> {
navigatorStateLabel.setText(Container.i18n().text(I18n.ASSETS_STATE_ERROR) + "\n\n" + state.errorMessage());
navigatorContent.getChildren().setAll(createNavigatorMessage(state.errorMessage()));
workspaceSummaryLabel.setText(Container.i18n().text(I18n.ASSETS_SUMMARY_ERROR));
detailStateLabel.setText(state.errorMessage());
}
case READY -> {
final int assetCount = state.assets().size();
navigatorStateLabel.setText(Container.i18n().format(I18n.ASSETS_STATE_READY, assetCount));
final AssetNavigatorProjection projection = AssetNavigatorProjectionBuilder.build(
state.assets(),
assetsRoot(),
searchQuery,
activeFilters);
if (projection.isEmpty()) {
navigatorStateLabel.setText(Container.i18n().text(I18n.ASSETS_STATE_NO_RESULTS));
navigatorContent.getChildren().setAll(createNavigatorMessage(Container.i18n().text(I18n.ASSETS_STATE_NO_RESULTS)));
} else {
navigatorStateLabel.setText(Container.i18n().format(I18n.ASSETS_STATE_READY, projection.visibleAssetCount(), assetCount));
renderNavigatorProjection(projection);
}
workspaceSummaryLabel.setText(Container.i18n().format(I18n.ASSETS_SUMMARY_READY, assetCount));
final String selectedDescription = state.selectedAsset()
.map(asset -> Container.i18n().format(
@ -146,6 +213,103 @@ public final class AssetWorkspace implements Workspace {
}
}
private void renderNavigatorProjection(AssetNavigatorProjection projection) {
navigatorContent.getChildren().clear();
for (AssetNavigatorGroup group : projection.groups()) {
final VBox groupBox = new VBox(6);
groupBox.getStyleClass().add("assets-workspace-group");
final Label groupLabel = new Label(group.label());
groupLabel.getStyleClass().add("assets-workspace-group-label");
groupBox.getChildren().add(groupLabel);
for (AssetWorkspaceAssetSummary asset : group.assets()) {
groupBox.getChildren().add(createAssetRow(asset));
}
navigatorContent.getChildren().add(groupBox);
}
}
private Node createAssetRow(AssetWorkspaceAssetSummary asset) {
final VBox row = new VBox(4);
row.getStyleClass().add("assets-workspace-asset-row");
if (asset.selectionKey().equals(state.selectedKey())) {
row.getStyleClass().add("assets-workspace-asset-row-selected");
}
final HBox topLine = new HBox(8);
topLine.setAlignment(javafx.geometry.Pos.CENTER_LEFT);
final Label icon = new Label(assetIcon(asset));
icon.getStyleClass().add("assets-workspace-asset-icon");
final Label name = new Label(asset.assetName());
name.getStyleClass().add("assets-workspace-asset-name");
final Region spacer = new Region();
HBox.setHgrow(spacer, Priority.ALWAYS);
final FlowPane badges = new FlowPane();
badges.setHgap(6);
badges.setVgap(4);
badges.getChildren().add(createBadge(
asset.state() == AssetWorkspaceAssetState.MANAGED
? Container.i18n().text(I18n.ASSETS_BADGE_MANAGED)
: Container.i18n().text(I18n.ASSETS_BADGE_ORPHAN),
asset.state() == AssetWorkspaceAssetState.MANAGED
? "assets-workspace-badge-managed"
: "assets-workspace-badge-orphan"));
badges.getChildren().add(createBadge(asset.assetFamily(), "assets-workspace-badge-family"));
if (asset.preload()) {
badges.getChildren().add(createBadge(Container.i18n().text(I18n.ASSETS_BADGE_PRELOAD), "assets-workspace-badge-preload"));
}
if (asset.hasDiagnostics()) {
badges.getChildren().add(createBadge(Container.i18n().text(I18n.ASSETS_BADGE_DIAGNOSTICS), "assets-workspace-badge-diagnostics"));
}
topLine.getChildren().addAll(icon, name, spacer, badges);
final Label path = new Label(AssetNavigatorProjectionBuilder.relativeRoot(asset, assetsRoot()));
path.getStyleClass().add("assets-workspace-asset-path");
row.getChildren().addAll(topLine, path);
row.setOnMouseClicked(event -> selectAsset(asset.selectionKey()));
return row;
}
private Node createBadge(String text, String styleClass) {
final Label badge = new Label(text);
badge.getStyleClass().add("assets-workspace-badge");
badge.getStyleClass().add(styleClass);
return badge;
}
private Node createNavigatorMessage(String text) {
final Label label = new Label(text);
label.getStyleClass().add("assets-workspace-empty-state");
label.setWrapText(true);
return label;
}
private void selectAsset(AssetWorkspaceSelectionKey selectionKey) {
state = state.withSelection(selectionKey);
renderState();
workspaceBus.publish(new StudioAssetsWorkspaceSelectionChangedEvent(projectReference, selectionKey));
}
private String assetIcon(AssetWorkspaceAssetSummary asset) {
final String family = asset.assetFamily().toLowerCase();
if (family.contains("image")) {
return "🖼";
}
if (family.contains("sound") || family.contains("audio")) {
return "🔊";
}
if (family.contains("palette")) {
return "🎨";
}
return "";
}
private Path assetsRoot() {
return projectReference.rootPath().resolve("assets").toAbsolutePath().normalize();
}
private String rootCauseMessage(Throwable throwable) {
Throwable current = throwable;
while (current.getCause() != null) {

View File

@ -70,10 +70,20 @@ workspace.shipper.button.clear=Clear
workspace.assets=Assets
assets.navigator.title=Asset Navigator
assets.details.title=Selected Asset
assets.search.prompt=Search assets by name or path
assets.filter.managed=Managed
assets.filter.orphan=Orphan
assets.filter.diagnostics=Diagnostics
assets.filter.preload=Preload
assets.state.loading=Loading assets...
assets.state.empty=No managed or orphan assets were found in this project.
assets.state.ready={0} assets loaded.
assets.state.noResults=No assets match the current search or filters.
assets.state.ready={0} visible assets ({1} total).
assets.state.error=Asset workspace failed to load.
assets.badge.managed=Managed
assets.badge.orphan=Orphan
assets.badge.preload=Preload
assets.badge.diagnostics=Diagnostics
assets.summary.loading=Hydrating asset workspace state...
assets.summary.empty=No assets are currently available.
assets.summary.ready=Navigator ready with {0} assets.

View File

@ -133,6 +133,34 @@
-fx-font-weight: bold;
}
.assets-workspace-search {
-fx-background-color: #101419;
-fx-text-fill: #f2f7fb;
-fx-prompt-text-fill: #7f8da0;
-fx-background-radius: 10;
-fx-border-color: #283240;
-fx-border-radius: 10;
}
.assets-workspace-filter-bar {
-fx-padding: 4 0 2 0;
}
.assets-workspace-filter-button {
-fx-background-color: #12161d;
-fx-text-fill: #c7d5e5;
-fx-background-radius: 999;
-fx-border-radius: 999;
-fx-border-color: #2d3948;
-fx-cursor: hand;
}
.assets-workspace-filter-button:selected {
-fx-background-color: #24415e;
-fx-border-color: #4d88bc;
-fx-text-fill: #ffffff;
}
.assets-workspace-summary {
-fx-text-fill: #9ecbff;
-fx-font-size: 12px;
@ -143,6 +171,107 @@
-fx-font-size: 12px;
}
.assets-workspace-navigator-scroll {
-fx-background-color: transparent;
-fx-fit-to-width: true;
}
.assets-workspace-navigator-scroll > .viewport {
-fx-background-color: transparent;
}
.assets-workspace-navigator-content {
-fx-padding: 4 0 4 0;
}
.assets-workspace-group {
-fx-spacing: 6;
}
.assets-workspace-group-label {
-fx-text-fill: #82a9d1;
-fx-font-size: 11px;
-fx-font-weight: bold;
}
.assets-workspace-asset-row {
-fx-background-color: #11151b;
-fx-background-radius: 10;
-fx-border-radius: 10;
-fx-border-color: #242d38;
-fx-padding: 10;
-fx-cursor: hand;
}
.assets-workspace-asset-row:hover {
-fx-background-color: #17202a;
}
.assets-workspace-asset-row-selected {
-fx-background-color: #1d2c3c;
-fx-border-color: #4f8dc3;
}
.assets-workspace-asset-icon {
-fx-font-size: 14px;
}
.assets-workspace-asset-name {
-fx-text-fill: #f6fbff;
-fx-font-size: 13px;
-fx-font-weight: bold;
}
.assets-workspace-asset-path {
-fx-text-fill: #9eacbb;
-fx-font-size: 11px;
}
.assets-workspace-badge {
-fx-font-size: 10px;
-fx-padding: 3 7 3 7;
-fx-background-radius: 999;
-fx-border-radius: 999;
-fx-border-width: 1;
}
.assets-workspace-badge-managed {
-fx-background-color: #153425;
-fx-border-color: #2f8f59;
-fx-text-fill: #8ce3ae;
}
.assets-workspace-badge-orphan {
-fx-background-color: #3a2d14;
-fx-border-color: #bc8a31;
-fx-text-fill: #ffd27a;
}
.assets-workspace-badge-family {
-fx-background-color: #1c2733;
-fx-border-color: #38506a;
-fx-text-fill: #b7d8f8;
}
.assets-workspace-badge-preload {
-fx-background-color: #271747;
-fx-border-color: #7f65cf;
-fx-text-fill: #d9cbff;
}
.assets-workspace-badge-diagnostics {
-fx-background-color: #4a1a1c;
-fx-border-color: #cf6268;
-fx-text-fill: #ffb4b8;
}
.assets-workspace-empty-state {
-fx-text-fill: #c4ced8;
-fx-font-size: 12px;
-fx-wrap-text: true;
-fx-padding: 10 0 0 0;
}
.studio-project-launcher {
-fx-background-color: linear-gradient(to bottom, #20242c, #14181d);
}

View File

@ -0,0 +1,116 @@
package p.studio.workspaces.assets;
import org.junit.jupiter.api.Test;
import java.nio.file.Path;
import java.util.EnumSet;
import java.util.List;
import static org.junit.jupiter.api.Assertions.assertEquals;
final class AssetNavigatorProjectionBuilderTest {
@Test
void groupsAssetsByParentPath() {
final Path assetsRoot = Path.of("/tmp/project/assets");
final AssetNavigatorProjection projection = AssetNavigatorProjectionBuilder.build(
List.of(
managedAsset(1, "ui_atlas", "image_bank", assetsRoot.resolve("ui/atlas"), true, false),
orphanAsset("menu_sounds", "sound_bank", assetsRoot.resolve("audio/menu"), false, false)),
assetsRoot,
"",
EnumSet.noneOf(AssetNavigatorFilter.class));
assertEquals(2, projection.visibleAssetCount());
assertEquals(List.of("audio", "ui"), projection.groups().stream().map(AssetNavigatorGroup::label).sorted().toList());
}
@Test
void managedAndOrphanFiltersBehaveAsStateFilterSet() {
final Path assetsRoot = Path.of("/tmp/project/assets");
final AssetNavigatorProjection projection = AssetNavigatorProjectionBuilder.build(
List.of(
managedAsset(1, "ui_atlas", "image_bank", assetsRoot.resolve("ui/atlas"), true, false),
orphanAsset("menu_sounds", "sound_bank", assetsRoot.resolve("audio/menu"), false, false)),
assetsRoot,
"",
EnumSet.of(AssetNavigatorFilter.MANAGED));
assertEquals(1, projection.visibleAssetCount());
assertEquals("ui_atlas", projection.groups().getFirst().assets().getFirst().assetName());
}
@Test
void diagnosticsAndPreloadActAsAdditionalConstraints() {
final Path assetsRoot = Path.of("/tmp/project/assets");
final AssetNavigatorProjection projection = AssetNavigatorProjectionBuilder.build(
List.of(
managedAsset(1, "ui_atlas", "image_bank", assetsRoot.resolve("ui/atlas"), true, true),
managedAsset(2, "bg_tiles", "image_bank", assetsRoot.resolve("bg/tiles"), true, false),
managedAsset(3, "voice_bank", "sound_bank", assetsRoot.resolve("audio/voice"), false, true)),
assetsRoot,
"",
EnumSet.of(AssetNavigatorFilter.MANAGED, AssetNavigatorFilter.PRELOAD, AssetNavigatorFilter.DIAGNOSTICS));
assertEquals(1, projection.visibleAssetCount());
assertEquals("ui_atlas", projection.groups().getFirst().assets().getFirst().assetName());
}
@Test
void searchMatchesAssetNameAndPathContext() {
final Path assetsRoot = Path.of("/tmp/project/assets");
final List<AssetWorkspaceAssetSummary> assets = List.of(
managedAsset(1, "ui_atlas", "image_bank", assetsRoot.resolve("ui/atlas"), true, false),
orphanAsset("menu_sounds", "sound_bank", assetsRoot.resolve("audio/menu"), false, false));
final AssetNavigatorProjection byName = AssetNavigatorProjectionBuilder.build(
assets,
assetsRoot,
"atlas",
EnumSet.noneOf(AssetNavigatorFilter.class));
final AssetNavigatorProjection byPath = AssetNavigatorProjectionBuilder.build(
assets,
assetsRoot,
"audio",
EnumSet.noneOf(AssetNavigatorFilter.class));
assertEquals(1, byName.visibleAssetCount());
assertEquals("ui_atlas", byName.groups().getFirst().assets().getFirst().assetName());
assertEquals(1, byPath.visibleAssetCount());
assertEquals("menu_sounds", byPath.groups().getFirst().assets().getFirst().assetName());
}
private AssetWorkspaceAssetSummary managedAsset(
int assetId,
String name,
String family,
Path root,
boolean preload,
boolean hasDiagnostics) {
return new AssetWorkspaceAssetSummary(
new AssetWorkspaceSelectionKey.ManagedAsset(assetId),
name,
AssetWorkspaceAssetState.MANAGED,
assetId,
family,
root,
preload,
hasDiagnostics);
}
private AssetWorkspaceAssetSummary orphanAsset(
String name,
String family,
Path root,
boolean preload,
boolean hasDiagnostics) {
return new AssetWorkspaceAssetSummary(
new AssetWorkspaceSelectionKey.OrphanAsset(root),
name,
AssetWorkspaceAssetState.ORPHAN,
null,
family,
root,
preload,
hasDiagnostics);
}
}