From 9cf59c3c1f987b8bc5f3cfce5b4fbfc6f2cf348a Mon Sep 17 00:00:00 2001 From: bQUARKz Date: Thu, 19 Mar 2026 09:28:06 +0000 Subject: [PATCH] asset details (WIP) --- .../assets/details/AssetDetailsControl.java | 21 +- .../assets/details/AssetDetailsUiSupport.java | 8 + .../dialogs/AssetDiagnosticsDialog.java | 127 ++++++++++- .../resources/themes/default-prometeu.css | 50 +++++ test-projects/main/.studio/activities.json | 200 +++++++++--------- 5 files changed, 292 insertions(+), 114 deletions(-) diff --git a/prometeu-studio/src/main/java/p/studio/workspaces/assets/details/AssetDetailsControl.java b/prometeu-studio/src/main/java/p/studio/workspaces/assets/details/AssetDetailsControl.java index 9bac5bb4..d16deb7c 100644 --- a/prometeu-studio/src/main/java/p/studio/workspaces/assets/details/AssetDetailsControl.java +++ b/prometeu-studio/src/main/java/p/studio/workspaces/assets/details/AssetDetailsControl.java @@ -314,16 +314,27 @@ public final class AssetDetailsControl extends VBox implements StudioEventAware nodes.add(AssetDetailsUiSupport.createSectionMessage(Container.i18n().text(I18n.ASSETS_ACTIONS_EMPTY))); return nodes; } - for (AssetWorkspaceAssetAction action : visibleActions) { + final Button analyseButton = AssetDetailsUiSupport.createActionButton(Container.i18n().text(I18n.ASSETS_ACTION_ANALYSE)); + AssetDetailsUiSupport.applyActionTone(analyseButton, "studio-button-warning"); + analyseButton.setDisable(actionRunning || viewState.selectedAssetDetails().diagnostics().isEmpty()); + analyseButton.setOnAction(ignored -> openDiagnosticsDialog()); + nodes.add(analyseButton); + + final var orderedActions = visibleActions.stream() + .sorted(java.util.Comparator.comparingInt(action -> switch (action.action()) { + case DELETE -> 2; + default -> 1; + })) + .toList(); + for (AssetWorkspaceAssetAction action : orderedActions) { final Button button = AssetDetailsUiSupport.createActionButton(AssetDetailsUiSupport.actionLabel(action.action())); + if (action.action() == p.packer.messages.assets.AssetAction.DELETE) { + AssetDetailsUiSupport.applyActionTone(button, "studio-button-danger"); + } button.setDisable(actionRunning || !action.enabled()); button.setOnAction(ignored -> executeAction(action)); nodes.add(button); } - final Button analyseButton = AssetDetailsUiSupport.createActionButton(Container.i18n().text(I18n.ASSETS_ACTION_ANALYSE)); - analyseButton.setDisable(viewState.selectedAssetDetails().diagnostics().isEmpty()); - analyseButton.setOnAction(ignored -> openDiagnosticsDialog()); - nodes.add(analyseButton); return nodes; } diff --git a/prometeu-studio/src/main/java/p/studio/workspaces/assets/details/AssetDetailsUiSupport.java b/prometeu-studio/src/main/java/p/studio/workspaces/assets/details/AssetDetailsUiSupport.java index 88c06e10..e228baea 100644 --- a/prometeu-studio/src/main/java/p/studio/workspaces/assets/details/AssetDetailsUiSupport.java +++ b/prometeu-studio/src/main/java/p/studio/workspaces/assets/details/AssetDetailsUiSupport.java @@ -81,6 +81,14 @@ public final class AssetDetailsUiSupport { return button; } + public static void applyActionTone(Button button, String toneClass) { + button.getStyleClass().removeAll( + "studio-button-primary", + "studio-button-warning", + "studio-button-danger"); + button.getStyleClass().add(toneClass); + } + public static String registrationLabel(AssetWorkspaceAssetState state) { return switch (state) { case REGISTERED -> Container.i18n().text(I18n.ASSETS_VALUE_REGISTERED); diff --git a/prometeu-studio/src/main/java/p/studio/workspaces/assets/dialogs/AssetDiagnosticsDialog.java b/prometeu-studio/src/main/java/p/studio/workspaces/assets/dialogs/AssetDiagnosticsDialog.java index 3483b134..f77e6d79 100644 --- a/prometeu-studio/src/main/java/p/studio/workspaces/assets/dialogs/AssetDiagnosticsDialog.java +++ b/prometeu-studio/src/main/java/p/studio/workspaces/assets/dialogs/AssetDiagnosticsDialog.java @@ -20,14 +20,23 @@ import p.studio.utilities.i18n.I18n; import p.studio.workspaces.assets.details.AssetDetailsUiSupport; import java.nio.file.Path; +import java.util.LinkedHashMap; import java.util.List; +import java.util.Map; import java.util.Objects; public final class AssetDiagnosticsDialog { + private static final String GENERAL_GROUP_KEY = "__general__"; + private final Stage stage; private final ProjectReference projectReference; private final String assetName; private final List diagnostics; + private final Map diagnosticsByGroup; + + private final VBox groupList = new VBox(6); + private final VBox diagnosticsContent = new VBox(10); + private String selectedGroupKey; private AssetDiagnosticsDialog( Window owner, @@ -37,6 +46,10 @@ public final class AssetDiagnosticsDialog { this.projectReference = Objects.requireNonNull(projectReference, "projectReference"); this.assetName = Objects.requireNonNull(assetName, "assetName"); this.diagnostics = List.copyOf(Objects.requireNonNull(diagnostics, "diagnostics")); + this.diagnosticsByGroup = buildGroups(this.diagnostics); + this.selectedGroupKey = diagnosticsByGroup.containsKey(GENERAL_GROUP_KEY) + ? GENERAL_GROUP_KEY + : diagnosticsByGroup.keySet().stream().findFirst().orElse(GENERAL_GROUP_KEY); this.stage = new Stage(); stage.initOwner(owner); stage.initModality(Modality.WINDOW_MODAL); @@ -64,20 +77,25 @@ public final class AssetDiagnosticsDialog { subtitle.getStyleClass().add("studio-launcher-subtitle"); subtitle.setWrapText(true); - final VBox diagnosticsContent = new VBox(10); diagnosticsContent.getStyleClass().add("assets-diagnostics-dialog-content"); - if (diagnostics.isEmpty()) { - diagnosticsContent.getChildren().add(AssetDetailsUiSupport.createSectionMessage( - Container.i18n().text(I18n.ASSETS_DIAGNOSTICS_EMPTY))); - } else { - diagnostics.forEach(diagnostic -> diagnosticsContent.getChildren().add(createDiagnosticCard(diagnostic))); - } final ScrollPane scrollPane = new ScrollPane(diagnosticsContent); scrollPane.setFitToWidth(true); scrollPane.setHbarPolicy(ScrollPane.ScrollBarPolicy.NEVER); scrollPane.getStyleClass().add("assets-diagnostics-dialog-scroll"); + groupList.getStyleClass().add("assets-diagnostics-dialog-groups"); + final ScrollPane groupScroll = new ScrollPane(groupList); + groupScroll.setFitToWidth(true); + groupScroll.setHbarPolicy(ScrollPane.ScrollBarPolicy.NEVER); + groupScroll.getStyleClass().add("assets-diagnostics-dialog-groups-scroll"); + + final HBox browser = new HBox(16, groupScroll, scrollPane); + browser.getStyleClass().add("assets-diagnostics-dialog-browser"); + HBox.setHgrow(scrollPane, Priority.ALWAYS); + renderGroups(); + renderSelectedGroup(); + final Button closeButton = new Button(); closeButton.textProperty().bind(Container.i18n().bind(I18n.WIZARD_CANCEL)); closeButton.getStyleClass().addAll("studio-button", "studio-button-cancel"); @@ -86,12 +104,49 @@ public final class AssetDiagnosticsDialog { final HBox actions = new HBox(closeButton); actions.setAlignment(Pos.CENTER_RIGHT); - final VBox root = new VBox(16, title, subtitle, scrollPane, actions); + final VBox root = new VBox(16, title, subtitle, browser, actions); root.setPadding(new Insets(24)); - VBox.setVgrow(scrollPane, Priority.ALWAYS); + VBox.setVgrow(browser, Priority.ALWAYS); return root; } + private void renderGroups() { + groupList.getChildren().clear(); + if (diagnosticsByGroup.isEmpty()) { + return; + } + for (DiagnosticGroup group : diagnosticsByGroup.values()) { + final Button button = new Button(group.title() + " (" + group.diagnostics().size() + ")"); + button.getStyleClass().addAll("assets-diagnostics-dialog-group-button"); + if (Objects.equals(selectedGroupKey, group.key())) { + button.getStyleClass().add("assets-diagnostics-dialog-group-button-selected"); + } + button.setMaxWidth(Double.MAX_VALUE); + button.setAlignment(Pos.CENTER_LEFT); + button.setOnAction(ignored -> { + selectedGroupKey = group.key(); + renderGroups(); + renderSelectedGroup(); + }); + groupList.getChildren().add(button); + } + } + + private void renderSelectedGroup() { + diagnosticsContent.getChildren().clear(); + final DiagnosticGroup group = diagnosticsByGroup.get(selectedGroupKey); + if (group == null || group.diagnostics().isEmpty()) { + diagnosticsContent.getChildren().add(AssetDetailsUiSupport.createSectionMessage( + Container.i18n().text(I18n.ASSETS_DIAGNOSTICS_EMPTY))); + return; + } + + final Label groupTitle = new Label(group.title()); + groupTitle.getStyleClass().add("assets-diagnostics-dialog-group-title"); + diagnosticsContent.getChildren().add(groupTitle); + group.diagnostics().forEach(diagnostic -> diagnosticsContent.getChildren().add(createDiagnosticCard(diagnostic))); + } + private VBox createDiagnosticCard(PackerDiagnosticDTO diagnostic) { final Label severity = new Label(severityLabel(diagnostic)); severity.getStyleClass().add("assets-details-diagnostic-severity"); @@ -135,4 +190,58 @@ public final class AssetDiagnosticsDialog { } return AssetDetailsUiSupport.projectRelativePath(projectReference, evidencePath); } + + private Map buildGroups(List diagnostics) { + final Map grouped = new LinkedHashMap<>(); + for (PackerDiagnosticDTO diagnostic : diagnostics) { + final String key = groupKey(diagnostic.evidencePath()); + final String title = groupTitle(key, diagnostic.evidencePath()); + grouped.computeIfAbsent(key, ignored -> new DiagnosticGroup(key, title, new java.util.ArrayList<>())) + .diagnostics() + .add(diagnostic); + } + final Map ordered = new LinkedHashMap<>(); + ordered.put(GENERAL_GROUP_KEY, grouped.getOrDefault( + GENERAL_GROUP_KEY, + new DiagnosticGroup(GENERAL_GROUP_KEY, "General", new java.util.ArrayList<>()))); + for (Map.Entry entry : grouped.entrySet()) { + if (!GENERAL_GROUP_KEY.equals(entry.getKey())) { + ordered.put(entry.getKey(), entry.getValue()); + } + } + return ordered.entrySet().stream() + .filter(entry -> !entry.getValue().diagnostics().isEmpty() || GENERAL_GROUP_KEY.equals(entry.getKey())) + .collect(java.util.stream.Collectors.toMap( + Map.Entry::getKey, + Map.Entry::getValue, + (left, right) -> left, + LinkedHashMap::new)); + } + + private String groupKey(Path evidencePath) { + if (evidencePath == null) { + return GENERAL_GROUP_KEY; + } + final String relativePath = AssetDetailsUiSupport.projectRelativePath(projectReference, evidencePath); + if (relativePath.endsWith("/asset.json") || relativePath.equals("asset.json")) { + return GENERAL_GROUP_KEY; + } + return relativePath; + } + + private String groupTitle(String key, Path evidencePath) { + if (GENERAL_GROUP_KEY.equals(key)) { + return "General"; + } + if (evidencePath == null) { + return "General"; + } + return evidencePath.getFileName() == null ? key : evidencePath.getFileName().toString(); + } + + private record DiagnosticGroup( + String key, + String title, + List diagnostics) { + } } diff --git a/prometeu-studio/src/main/resources/themes/default-prometeu.css b/prometeu-studio/src/main/resources/themes/default-prometeu.css index 731a46ca..cacfbfd0 100644 --- a/prometeu-studio/src/main/resources/themes/default-prometeu.css +++ b/prometeu-studio/src/main/resources/themes/default-prometeu.css @@ -585,6 +585,56 @@ -fx-spacing: 10; } +.assets-diagnostics-dialog-browser { + -fx-alignment: top-left; +} + +.assets-diagnostics-dialog-groups-scroll { + -fx-background-color: #0f1318; + -fx-background-radius: 10; + -fx-border-color: #2f3a47; + -fx-border-radius: 10; + -fx-min-width: 220; + -fx-pref-width: 220; + -fx-max-width: 220; +} + +.assets-diagnostics-dialog-groups-scroll > .viewport { + -fx-background-color: transparent; +} + +.assets-diagnostics-dialog-groups { + -fx-padding: 10; + -fx-spacing: 6; +} + +.assets-diagnostics-dialog-group-button { + -fx-background-color: #141b22; + -fx-border-color: #2f3a47; + -fx-border-radius: 8; + -fx-background-radius: 8; + -fx-text-fill: #d9e5f1; + -fx-padding: 8 10 8 10; + -fx-font-size: 12px; +} + +.assets-diagnostics-dialog-group-button:hover { + -fx-background-color: #1a242d; + -fx-border-color: #49627a; +} + +.assets-diagnostics-dialog-group-button-selected { + -fx-background-color: #203549; + -fx-border-color: #6ca8d8; + -fx-text-fill: #f4fbff; +} + +.assets-diagnostics-dialog-group-title { + -fx-text-fill: #eef4fb; + -fx-font-size: 14px; + -fx-font-weight: bold; +} + .assets-details-action-button { -fx-max-width: Infinity; -fx-padding: 6 10 6 10; diff --git a/test-projects/main/.studio/activities.json b/test-projects/main/.studio/activities.json index c54802ab..9d97d0f0 100644 --- a/test-projects/main/.studio/activities.json +++ b/test-projects/main/.studio/activities.json @@ -58,6 +58,106 @@ "message" : "Asset scan diagnostics updated.", "severity" : "INFO", "sticky" : false +}, { + "source" : "Assets", + "message" : "Discovered asset: bla", + "severity" : "INFO", + "sticky" : false +}, { + "source" : "Assets", + "message" : "Discovered asset: one-more-atlas", + "severity" : "INFO", + "sticky" : false +}, { + "source" : "Assets", + "message" : "Discovered asset: ui_atlas", + "severity" : "INFO", + "sticky" : false +}, { + "source" : "Assets", + "message" : "Discovered asset: one-more-atlas", + "severity" : "INFO", + "sticky" : false +}, { + "source" : "Assets", + "message" : "Discovered asset: bbb2", + "severity" : "INFO", + "sticky" : false +}, { + "source" : "Assets", + "message" : "Discovered asset: ui_atlas", + "severity" : "INFO", + "sticky" : false +}, { + "source" : "Assets", + "message" : "Discovered asset: Bigode", + "severity" : "INFO", + "sticky" : false +}, { + "source" : "Assets", + "message" : "Asset scan started", + "severity" : "INFO", + "sticky" : false +}, { + "source" : "Assets", + "message" : "7 assets loaded", + "severity" : "SUCCESS", + "sticky" : false +}, { + "source" : "Assets", + "message" : "Asset scan diagnostics updated.", + "severity" : "INFO", + "sticky" : false +}, { + "source" : "Assets", + "message" : "Discovered asset: bla", + "severity" : "INFO", + "sticky" : false +}, { + "source" : "Assets", + "message" : "Discovered asset: one-more-atlas", + "severity" : "INFO", + "sticky" : false +}, { + "source" : "Assets", + "message" : "Discovered asset: ui_atlas", + "severity" : "INFO", + "sticky" : false +}, { + "source" : "Assets", + "message" : "Discovered asset: one-more-atlas", + "severity" : "INFO", + "sticky" : false +}, { + "source" : "Assets", + "message" : "Discovered asset: bbb2", + "severity" : "INFO", + "sticky" : false +}, { + "source" : "Assets", + "message" : "Discovered asset: ui_atlas", + "severity" : "INFO", + "sticky" : false +}, { + "source" : "Assets", + "message" : "Discovered asset: Bigode", + "severity" : "INFO", + "sticky" : false +}, { + "source" : "Assets", + "message" : "Asset scan started", + "severity" : "INFO", + "sticky" : false +}, { + "source" : "Assets", + "message" : "7 assets loaded", + "severity" : "SUCCESS", + "sticky" : false +}, { + "source" : "Assets", + "message" : "Asset scan diagnostics updated.", + "severity" : "INFO", + "sticky" : false }, { "source" : "Assets", "message" : "Discovered asset: sound", @@ -2398,104 +2498,4 @@ "message" : "Asset scan started", "severity" : "INFO", "sticky" : false -}, { - "source" : "Assets", - "message" : "7 assets loaded", - "severity" : "SUCCESS", - "sticky" : false -}, { - "source" : "Assets", - "message" : "Asset scan diagnostics updated.", - "severity" : "INFO", - "sticky" : false -}, { - "source" : "Assets", - "message" : "Discovered asset: bla", - "severity" : "INFO", - "sticky" : false -}, { - "source" : "Assets", - "message" : "Discovered asset: one-more-atlas", - "severity" : "INFO", - "sticky" : false -}, { - "source" : "Assets", - "message" : "Discovered asset: ui_atlas", - "severity" : "INFO", - "sticky" : false -}, { - "source" : "Assets", - "message" : "Discovered asset: one-more-atlas", - "severity" : "INFO", - "sticky" : false -}, { - "source" : "Assets", - "message" : "Discovered asset: bbb2", - "severity" : "INFO", - "sticky" : false -}, { - "source" : "Assets", - "message" : "Discovered asset: ui_atlas", - "severity" : "INFO", - "sticky" : false -}, { - "source" : "Assets", - "message" : "Discovered asset: Bigode", - "severity" : "INFO", - "sticky" : false -}, { - "source" : "Assets", - "message" : "Asset scan started", - "severity" : "INFO", - "sticky" : false -}, { - "source" : "Assets", - "message" : "7 assets loaded", - "severity" : "SUCCESS", - "sticky" : false -}, { - "source" : "Assets", - "message" : "Asset scan diagnostics updated.", - "severity" : "INFO", - "sticky" : false -}, { - "source" : "Assets", - "message" : "Discovered asset: bla", - "severity" : "INFO", - "sticky" : false -}, { - "source" : "Assets", - "message" : "Discovered asset: one-more-atlas", - "severity" : "INFO", - "sticky" : false -}, { - "source" : "Assets", - "message" : "Discovered asset: ui_atlas", - "severity" : "INFO", - "sticky" : false -}, { - "source" : "Assets", - "message" : "Discovered asset: one-more-atlas", - "severity" : "INFO", - "sticky" : false -}, { - "source" : "Assets", - "message" : "Discovered asset: bbb2", - "severity" : "INFO", - "sticky" : false -}, { - "source" : "Assets", - "message" : "Discovered asset: ui_atlas", - "severity" : "INFO", - "sticky" : false -}, { - "source" : "Assets", - "message" : "Discovered asset: Bigode", - "severity" : "INFO", - "sticky" : false -}, { - "source" : "Assets", - "message" : "Asset scan started", - "severity" : "INFO", - "sticky" : false } ] \ No newline at end of file