asset metadata form added
This commit is contained in:
parent
f34c5a7691
commit
4050fcbcaa
@ -14,6 +14,7 @@ public record PackerAssetDetailsDTO(
|
||||
OutputCodecCatalog outputCodec,
|
||||
List<OutputCodecCatalog> availableOutputCodecs,
|
||||
Map<OutputCodecCatalog, List<PackerCodecConfigurationFieldDTO>> codecConfigurationFieldsByCodec,
|
||||
List<PackerCodecConfigurationFieldDTO> metadataFields,
|
||||
Map<String, List<Path>> inputsByRole,
|
||||
List<PackerDiagnosticDTO> diagnostics) {
|
||||
|
||||
@ -23,6 +24,7 @@ public record PackerAssetDetailsDTO(
|
||||
outputCodec = Objects.requireNonNullElse(outputCodec, OutputCodecCatalog.UNKNOWN);
|
||||
availableOutputCodecs = List.copyOf(Objects.requireNonNull(availableOutputCodecs, "availableOutputCodecs"));
|
||||
codecConfigurationFieldsByCodec = Map.copyOf(Objects.requireNonNull(codecConfigurationFieldsByCodec, "codecConfigurationFieldsByCodec"));
|
||||
metadataFields = List.copyOf(Objects.requireNonNull(metadataFields, "metadataFields"));
|
||||
inputsByRole = Map.copyOf(Objects.requireNonNull(inputsByRole, "inputsByRole"));
|
||||
diagnostics = List.copyOf(Objects.requireNonNull(diagnostics, "diagnostics"));
|
||||
}
|
||||
|
||||
@ -10,12 +10,14 @@ public record UpdateAssetContractRequest(
|
||||
AssetReference assetReference,
|
||||
boolean preloadEnabled,
|
||||
OutputCodecCatalog outputCodec,
|
||||
Map<String, String> codecFieldValues) {
|
||||
Map<String, String> codecFieldValues,
|
||||
Map<String, String> metadataFieldValues) {
|
||||
|
||||
public UpdateAssetContractRequest {
|
||||
Objects.requireNonNull(project, "project");
|
||||
Objects.requireNonNull(assetReference, "assetReference");
|
||||
outputCodec = Objects.requireNonNullElse(outputCodec, OutputCodecCatalog.UNKNOWN);
|
||||
codecFieldValues = Map.copyOf(Objects.requireNonNullElse(codecFieldValues, Map.of()));
|
||||
metadataFieldValues = Map.copyOf(Objects.requireNonNullElse(metadataFieldValues, Map.of()));
|
||||
}
|
||||
}
|
||||
|
||||
@ -17,6 +17,7 @@ public record PackerAssetDeclaration(
|
||||
Map<String, List<String>> inputsByRole,
|
||||
OutputFormatCatalog outputFormat,
|
||||
OutputCodecCatalog outputCodec,
|
||||
Map<String, String> outputMetadata,
|
||||
boolean preloadEnabled) {
|
||||
|
||||
public PackerAssetDeclaration {
|
||||
@ -29,6 +30,7 @@ public record PackerAssetDeclaration(
|
||||
inputsByRole = Map.copyOf(Objects.requireNonNull(inputsByRole, "inputsByRole"));
|
||||
outputFormat = Objects.requireNonNull(outputFormat, "outputFormat");
|
||||
outputCodec = Objects.requireNonNull(outputCodec, "outputCodec");
|
||||
outputMetadata = Map.copyOf(Objects.requireNonNull(outputMetadata, "outputMetadata"));
|
||||
if (StringUtils.isBlank(assetUuid) || StringUtils.isBlank(name)) {
|
||||
throw new IllegalArgumentException("declaration fields must not be blank");
|
||||
}
|
||||
|
||||
@ -14,6 +14,7 @@ public record PackerAssetDetails(
|
||||
OutputCodecCatalog outputCodec,
|
||||
List<OutputCodecCatalog> availableOutputCodecs,
|
||||
Map<OutputCodecCatalog, List<PackerCodecConfigurationField>> codecConfigurationFieldsByCodec,
|
||||
List<PackerCodecConfigurationField> metadataFields,
|
||||
Map<String, List<Path>> inputsByRole,
|
||||
List<PackerDiagnostic> diagnostics) {
|
||||
|
||||
@ -23,6 +24,7 @@ public record PackerAssetDetails(
|
||||
outputCodec = Objects.requireNonNullElse(outputCodec, OutputCodecCatalog.UNKNOWN);
|
||||
availableOutputCodecs = List.copyOf(Objects.requireNonNull(availableOutputCodecs, "availableOutputCodecs"));
|
||||
codecConfigurationFieldsByCodec = Map.copyOf(Objects.requireNonNull(codecConfigurationFieldsByCodec, "codecConfigurationFieldsByCodec"));
|
||||
metadataFields = List.copyOf(Objects.requireNonNull(metadataFields, "metadataFields"));
|
||||
inputsByRole = Map.copyOf(Objects.requireNonNull(inputsByRole, "inputsByRole"));
|
||||
diagnostics = List.copyOf(Objects.requireNonNull(diagnostics, "diagnostics"));
|
||||
}
|
||||
|
||||
@ -672,6 +672,17 @@ public final class FileSystemPackerWorkspaceService implements PackerWorkspaceSe
|
||||
}
|
||||
codecConfigurationNode.put(normalizedFieldKey, Objects.requireNonNullElse(fieldValue, ""));
|
||||
});
|
||||
|
||||
final ObjectNode metadataNode = mutableObject(outputNode, "metadata");
|
||||
metadataNode.removeAll();
|
||||
request.metadataFieldValues().entrySet().stream()
|
||||
.sorted(Map.Entry.comparingByKey())
|
||||
.forEach(entry -> {
|
||||
if (entry.getKey() == null || entry.getKey().isBlank()) {
|
||||
return;
|
||||
}
|
||||
metadataNode.put(entry.getKey().trim(), Objects.requireNonNullElse(entry.getValue(), ""));
|
||||
});
|
||||
}
|
||||
|
||||
private ObjectNode mutableObject(ObjectNode parent, String fieldName) {
|
||||
|
||||
@ -47,6 +47,7 @@ public final class PackerAssetDeclarationParser {
|
||||
final Map<String, List<String>> inputsByRole = requiredInputs(root.path("inputs"), diagnostics, manifestPath);
|
||||
final OutputFormatCatalog outputFormat = requiredOutputFormat(root.path("output"), diagnostics, manifestPath);
|
||||
final OutputCodecCatalog outputCodec = requiredOutputCodec(root.path("output"), diagnostics, manifestPath);
|
||||
final Map<String, String> outputMetadata = optionalOutputMetadata(root.path("output"), diagnostics, manifestPath);
|
||||
final Boolean preloadEnabled = requiredBoolean(root.path("preload"), "enabled", diagnostics, manifestPath);
|
||||
|
||||
if (schemaVersion != null && schemaVersion != SUPPORTED_SCHEMA_VERSION) {
|
||||
@ -71,6 +72,7 @@ public final class PackerAssetDeclarationParser {
|
||||
inputsByRole,
|
||||
outputFormat,
|
||||
outputCodec,
|
||||
outputMetadata,
|
||||
preloadEnabled),
|
||||
diagnostics);
|
||||
}
|
||||
@ -156,6 +158,53 @@ public final class PackerAssetDeclarationParser {
|
||||
return outputCodec;
|
||||
}
|
||||
|
||||
private Map<String, String> optionalOutputMetadata(
|
||||
JsonNode outputNode,
|
||||
List<PackerDiagnostic> diagnostics,
|
||||
Path manifestPath) {
|
||||
final JsonNode metadataNode = outputNode.path("metadata");
|
||||
if (metadataNode.isMissingNode() || metadataNode.isNull()) {
|
||||
return Map.of();
|
||||
}
|
||||
if (!metadataNode.isObject()) {
|
||||
diagnostics.add(new PackerDiagnostic(
|
||||
PackerDiagnosticSeverity.ERROR,
|
||||
PackerDiagnosticCategory.STRUCTURAL,
|
||||
"Field 'output.metadata' must be a JSON object.",
|
||||
manifestPath,
|
||||
true));
|
||||
return Map.of();
|
||||
}
|
||||
|
||||
final Map<String, String> metadata = new LinkedHashMap<>();
|
||||
metadataNode.fields().forEachRemaining(entry -> {
|
||||
final String key = Objects.requireNonNullElse(entry.getKey(), "").trim();
|
||||
if (key.isBlank()) {
|
||||
diagnostics.add(new PackerDiagnostic(
|
||||
PackerDiagnosticSeverity.ERROR,
|
||||
PackerDiagnosticCategory.STRUCTURAL,
|
||||
"Field 'output.metadata' has an invalid empty key.",
|
||||
manifestPath,
|
||||
true));
|
||||
return;
|
||||
}
|
||||
|
||||
final JsonNode valueNode = entry.getValue();
|
||||
if (valueNode == null || valueNode.isContainerNode()) {
|
||||
diagnostics.add(new PackerDiagnostic(
|
||||
PackerDiagnosticSeverity.ERROR,
|
||||
PackerDiagnosticCategory.STRUCTURAL,
|
||||
"Field 'output.metadata' values must be scalar (text/number/boolean).",
|
||||
manifestPath,
|
||||
true));
|
||||
return;
|
||||
}
|
||||
metadata.put(key, valueNode.asText());
|
||||
});
|
||||
|
||||
return Map.copyOf(metadata);
|
||||
}
|
||||
|
||||
private Map<String, List<String>> requiredInputs(JsonNode inputsNode, List<PackerDiagnostic> diagnostics, Path manifestPath) {
|
||||
if (!inputsNode.isObject()) {
|
||||
diagnostics.add(missingOrInvalid("inputs", "object of input roles", manifestPath));
|
||||
|
||||
@ -73,6 +73,7 @@ public final class PackerAssetDetailsService {
|
||||
declaration.outputCodec(),
|
||||
outputContract.availableCodecs(),
|
||||
outputContract.codecConfigurationFieldsByCodec(),
|
||||
metadataFields(declaration.outputMetadata()),
|
||||
resolveInputs(resolved.assetRoot(), declaration.inputsByRole()),
|
||||
diagnostics);
|
||||
return new GetAssetDetailsResult(
|
||||
@ -111,6 +112,7 @@ public final class PackerAssetDetailsService {
|
||||
OutputCodecCatalog.UNKNOWN,
|
||||
List.of(OutputCodecCatalog.NONE),
|
||||
Map.of(OutputCodecCatalog.NONE, List.of()),
|
||||
List.of(),
|
||||
Map.of(),
|
||||
diagnostics);
|
||||
return new GetAssetDetailsResult(
|
||||
@ -130,6 +132,18 @@ public final class PackerAssetDetailsService {
|
||||
return Map.copyOf(resolved);
|
||||
}
|
||||
|
||||
private List<PackerCodecConfigurationField> metadataFields(Map<String, String> outputMetadata) {
|
||||
return outputMetadata.entrySet().stream()
|
||||
.map(entry -> new PackerCodecConfigurationField(
|
||||
entry.getKey(),
|
||||
entry.getKey(),
|
||||
PackerCodecConfigurationFieldType.TEXT,
|
||||
entry.getValue(),
|
||||
false,
|
||||
List.of()))
|
||||
.toList();
|
||||
}
|
||||
|
||||
private List<PackerDiagnostic> identityMismatchDiagnostics(
|
||||
final Optional<PackerRegistryEntry> registryEntry,
|
||||
final PackerAssetDeclaration declaration,
|
||||
|
||||
@ -30,6 +30,7 @@ public final class PackerReadMessageMapper {
|
||||
details.outputCodec(),
|
||||
details.availableOutputCodecs(),
|
||||
toCodecConfigurationFieldsByCodecDTO(details.codecConfigurationFieldsByCodec()),
|
||||
toCodecConfigurationFieldDTOs(details.metadataFields()),
|
||||
details.inputsByRole(),
|
||||
toDiagnosticDTOs(details.diagnostics()));
|
||||
}
|
||||
@ -74,6 +75,11 @@ public final class PackerReadMessageMapper {
|
||||
field.options());
|
||||
}
|
||||
|
||||
private static List<PackerCodecConfigurationFieldDTO> toCodecConfigurationFieldDTOs(
|
||||
List<PackerCodecConfigurationField> fields) {
|
||||
return fields.stream().map(PackerReadMessageMapper::toCodecConfigurationFieldDTO).toList();
|
||||
}
|
||||
|
||||
private static PackerDiagnosticDTO toDiagnosticDTO(PackerDiagnostic diagnostic) {
|
||||
return new PackerDiagnosticDTO(
|
||||
diagnostic.severity(),
|
||||
|
||||
@ -191,7 +191,10 @@ final class FileSystemPackerWorkspaceServiceTest {
|
||||
OutputCodecCatalog.NONE,
|
||||
Map.of(
|
||||
"NONE:packMode", "tight",
|
||||
"NONE:palette", "mono")));
|
||||
"NONE:palette", "mono"),
|
||||
Map.of(
|
||||
"tile_size", "16",
|
||||
"channels", "1")));
|
||||
|
||||
assertTrue(result.success());
|
||||
assertNull(result.errorMessage());
|
||||
@ -202,6 +205,8 @@ final class FileSystemPackerWorkspaceServiceTest {
|
||||
assertEquals("NONE", manifest.path("output").path("codec").asText());
|
||||
assertEquals("tight", manifest.path("output").path("codec_configuration").path("packMode").asText());
|
||||
assertEquals("mono", manifest.path("output").path("codec_configuration").path("palette").asText());
|
||||
assertEquals("16", manifest.path("output").path("metadata").path("tile_size").asText());
|
||||
assertEquals("1", manifest.path("output").path("metadata").path("channels").asText());
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -218,7 +223,8 @@ final class FileSystemPackerWorkspaceServiceTest {
|
||||
AssetReference.forAssetId(1),
|
||||
false,
|
||||
OutputCodecCatalog.NONE,
|
||||
Map.of("NONE:packMode", "tight")));
|
||||
Map.of("NONE:packMode", "tight"),
|
||||
Map.of()));
|
||||
|
||||
assertTrue(updateResult.success());
|
||||
assertEquals(1, loader.loadCount());
|
||||
@ -243,6 +249,7 @@ final class FileSystemPackerWorkspaceServiceTest {
|
||||
AssetReference.forAssetId(1),
|
||||
true,
|
||||
OutputCodecCatalog.NONE,
|
||||
Map.of(),
|
||||
Map.of()));
|
||||
|
||||
assertFalse(result.success());
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
package p.packer.services;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
import p.packer.messages.AssetReference;
|
||||
@ -16,6 +17,7 @@ import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
@ -57,6 +59,30 @@ final class PackerAssetDetailsServiceTest {
|
||||
assertEquals(List.of(), result.details().codecConfigurationFieldsByCodec().get(OutputCodecCatalog.NONE));
|
||||
}
|
||||
|
||||
@Test
|
||||
void exposesMetadataFieldsFromOutputMetadataContract() throws Exception {
|
||||
final Path projectRoot = copyFixture("workspaces/managed-basic", tempDir.resolve("managed-with-metadata"));
|
||||
final Path manifestPath = projectRoot.resolve("assets/ui/atlas/asset.json");
|
||||
|
||||
final ObjectMapper mapper = new ObjectMapper();
|
||||
final var manifest = mapper.readTree(manifestPath.toFile());
|
||||
final ObjectNode output = (ObjectNode) manifest.path("output");
|
||||
final ObjectNode metadata = output.putObject("metadata");
|
||||
metadata.put("tile_size", "16");
|
||||
metadata.put("channels", "1");
|
||||
mapper.writerWithDefaultPrettyPrinter().writeValue(manifestPath.toFile(), manifest);
|
||||
|
||||
final PackerAssetDetailsService service = service();
|
||||
final var result = service.getAssetDetails(new GetAssetDetailsRequest(project(projectRoot), AssetReference.forAssetId(1)));
|
||||
|
||||
assertEquals(PackerOperationStatus.SUCCESS, result.status());
|
||||
assertEquals(
|
||||
Map.of("tile_size", "16", "channels", "1"),
|
||||
result.details().metadataFields().stream().collect(java.util.stream.Collectors.toMap(
|
||||
field -> field.key(),
|
||||
field -> field.value())));
|
||||
}
|
||||
|
||||
@Test
|
||||
void returnsInvalidDetailsForInvalidDeclaration() throws Exception {
|
||||
final Path projectRoot = copyFixture("workspaces/invalid-missing-fields", tempDir.resolve("invalid"));
|
||||
|
||||
@ -97,6 +97,7 @@ public enum I18n {
|
||||
ASSETS_SECTION_SUMMARY("assets.section.summary"),
|
||||
ASSETS_SECTION_RUNTIME_CONTRACT("assets.section.runtimeContract"),
|
||||
ASSETS_SUBSECTION_CODEC_CONFIGURATION("assets.subsection.codecConfiguration"),
|
||||
ASSETS_SUBSECTION_METADATA("assets.subsection.metadata"),
|
||||
ASSETS_SECTION_INPUTS_PREVIEW("assets.section.inputsPreview"),
|
||||
ASSETS_SECTION_DIAGNOSTICS("assets.section.diagnostics"),
|
||||
ASSETS_SECTION_ACTIONS("assets.section.actions"),
|
||||
@ -177,6 +178,7 @@ public enum I18n {
|
||||
ASSETS_DETAILS_READY("assets.details.ready"),
|
||||
ASSETS_DETAILS_NO_SELECTION("assets.details.noSelection"),
|
||||
ASSETS_DETAILS_CODEC_CONFIGURATION_EMPTY("assets.details.codecConfiguration.empty"),
|
||||
ASSETS_DETAILS_METADATA_EMPTY("assets.details.metadata.empty"),
|
||||
ASSETS_ADD_WIZARD_TITLE("assets.addWizard.title"),
|
||||
ASSETS_ADD_WIZARD_DESCRIPTION("assets.addWizard.description"),
|
||||
ASSETS_ADD_WIZARD_STEP_ROOT_TITLE("assets.addWizard.step.root.title"),
|
||||
|
||||
@ -416,6 +416,7 @@ public final class AssetDetailsControl extends VBox implements StudioEventAware
|
||||
details.outputCodec(),
|
||||
details.availableOutputCodecs(),
|
||||
details.codecConfigurationFieldsByCodec(),
|
||||
details.metadataFields(),
|
||||
Map.copyOf(details.inputsByRole()));
|
||||
}
|
||||
}
|
||||
|
||||
@ -14,45 +14,60 @@ public record AssetContractDraft(
|
||||
boolean preload,
|
||||
OutputFormatCatalog bank,
|
||||
OutputCodecCatalog selectedCodec,
|
||||
Map<String, String> codecFieldValues) {
|
||||
Map<String, String> codecFieldValues,
|
||||
Map<String, String> metadataFieldValues) {
|
||||
|
||||
public AssetContractDraft {
|
||||
Objects.requireNonNull(assetReference, "assetReference");
|
||||
bank = Objects.requireNonNullElse(bank, OutputFormatCatalog.UNKNOWN);
|
||||
selectedCodec = Objects.requireNonNullElse(selectedCodec, OutputCodecCatalog.UNKNOWN);
|
||||
codecFieldValues = Map.copyOf(Objects.requireNonNull(codecFieldValues, "codecFieldValues"));
|
||||
metadataFieldValues = Map.copyOf(Objects.requireNonNull(metadataFieldValues, "metadataFieldValues"));
|
||||
}
|
||||
|
||||
public static AssetContractDraft fromDetails(AssetWorkspaceAssetDetails details) {
|
||||
final Map<String, String> codecFieldValues = new LinkedHashMap<>();
|
||||
details.codecConfigurationFieldsByCodec().forEach((codec, fields) ->
|
||||
fields.forEach(field -> codecFieldValues.put(keyOf(codec, field.key()), field.value())));
|
||||
final Map<String, String> metadataFieldValues = new LinkedHashMap<>();
|
||||
details.metadataFields().forEach(field -> metadataFieldValues.put(field.key(), field.value()));
|
||||
return new AssetContractDraft(
|
||||
details.summary().assetReference(),
|
||||
details.summary().preload(),
|
||||
details.outputFormat(),
|
||||
details.outputCodec(),
|
||||
codecFieldValues);
|
||||
codecFieldValues,
|
||||
metadataFieldValues);
|
||||
}
|
||||
|
||||
public AssetContractDraft withPreload(boolean preload) {
|
||||
return new AssetContractDraft(assetReference, preload, bank, selectedCodec, codecFieldValues);
|
||||
return new AssetContractDraft(assetReference, preload, bank, selectedCodec, codecFieldValues, metadataFieldValues);
|
||||
}
|
||||
|
||||
public AssetContractDraft withSelectedCodec(OutputCodecCatalog selectedCodec) {
|
||||
return new AssetContractDraft(assetReference, preload, bank, selectedCodec, codecFieldValues);
|
||||
return new AssetContractDraft(assetReference, preload, bank, selectedCodec, codecFieldValues, metadataFieldValues);
|
||||
}
|
||||
|
||||
public AssetContractDraft withCodecFieldValue(OutputCodecCatalog codec, String fieldKey, String value) {
|
||||
final Map<String, String> nextValues = new LinkedHashMap<>(codecFieldValues);
|
||||
nextValues.put(keyOf(codec, fieldKey), Objects.requireNonNullElse(value, ""));
|
||||
return new AssetContractDraft(assetReference, preload, bank, selectedCodec, nextValues);
|
||||
return new AssetContractDraft(assetReference, preload, bank, selectedCodec, nextValues, metadataFieldValues);
|
||||
}
|
||||
|
||||
public AssetContractDraft withMetadataFieldValue(String fieldKey, String value) {
|
||||
final Map<String, String> nextValues = new LinkedHashMap<>(metadataFieldValues);
|
||||
nextValues.put(fieldKey, Objects.requireNonNullElse(value, ""));
|
||||
return new AssetContractDraft(assetReference, preload, bank, selectedCodec, codecFieldValues, nextValues);
|
||||
}
|
||||
|
||||
public String codecFieldValue(OutputCodecCatalog codec, String fieldKey, String fallback) {
|
||||
return codecFieldValues.getOrDefault(keyOf(codec, fieldKey), Objects.requireNonNullElse(fallback, ""));
|
||||
}
|
||||
|
||||
public String metadataFieldValue(String fieldKey, String fallback) {
|
||||
return metadataFieldValues.getOrDefault(fieldKey, Objects.requireNonNullElse(fallback, ""));
|
||||
}
|
||||
|
||||
private static String keyOf(OutputCodecCatalog codec, String fieldKey) {
|
||||
return codec.name() + ":" + fieldKey;
|
||||
}
|
||||
|
||||
@ -105,7 +105,8 @@ public final class AssetDetailsContractControl extends VBox implements StudioCon
|
||||
createPreloadEditor(draft, editing)),
|
||||
AssetDetailsUiSupport.createKeyValueRow(
|
||||
Container.i18n().text(I18n.ASSETS_LABEL_BANK),
|
||||
draft.bank()));
|
||||
draft.bank()),
|
||||
createMetadataSection(details, draft, editing));
|
||||
|
||||
final VBox codecColumn = new VBox(10);
|
||||
codecColumn.getStyleClass().addAll("assets-details-contract-column", "assets-details-contract-codec-column");
|
||||
@ -220,6 +221,110 @@ public final class AssetDetailsContractControl extends VBox implements StudioCon
|
||||
return createCodecMetadataPane(title, content);
|
||||
}
|
||||
|
||||
private Node createMetadataSection(
|
||||
AssetWorkspaceAssetDetails details,
|
||||
AssetContractDraft draft,
|
||||
boolean editing) {
|
||||
final VBox subsection = new VBox(8);
|
||||
subsection.getStyleClass().add("assets-details-contract-metadata");
|
||||
final Label title = new Label(Container.i18n().text(I18n.ASSETS_SUBSECTION_METADATA));
|
||||
title.getStyleClass().add("assets-details-subsection-title");
|
||||
final VBox content = new VBox(8);
|
||||
content.getStyleClass().add("assets-details-contract-metadata-content");
|
||||
|
||||
if (details.metadataFields().isEmpty()) {
|
||||
content.getChildren().add(AssetDetailsUiSupport.createSectionMessage(
|
||||
Container.i18n().text(I18n.ASSETS_DETAILS_METADATA_EMPTY)));
|
||||
return createCodecMetadataPane(title, content);
|
||||
}
|
||||
|
||||
for (PackerCodecConfigurationFieldDTO field : details.metadataFields()) {
|
||||
content.getChildren().add(createMetadataFieldRow(field, draft, editing));
|
||||
}
|
||||
return createCodecMetadataPane(title, content);
|
||||
}
|
||||
|
||||
private Node createMetadataFieldRow(
|
||||
PackerCodecConfigurationFieldDTO field,
|
||||
AssetContractDraft draft,
|
||||
boolean editing) {
|
||||
final Node valueNode = switch (field.fieldType()) {
|
||||
case BOOLEAN -> createMetadataBooleanField(field, draft, editing);
|
||||
case ENUM -> createMetadataEnumField(field, draft, editing);
|
||||
case INTEGER, TEXT -> createMetadataTextField(field, draft, editing);
|
||||
};
|
||||
return AssetDetailsUiSupport.createKeyValueRow(field.label(), valueNode);
|
||||
}
|
||||
|
||||
private Node createMetadataBooleanField(
|
||||
PackerCodecConfigurationFieldDTO field,
|
||||
AssetContractDraft draft,
|
||||
boolean editing) {
|
||||
final CheckBox checkBox = new CheckBox();
|
||||
checkBox.getStyleClass().add("assets-details-readonly-check");
|
||||
checkBox.setSelected(Boolean.parseBoolean(currentMetadataValue(field, draft)));
|
||||
if (editing) {
|
||||
checkBox.selectedProperty().addListener((ignored, oldValue, newValue) -> {
|
||||
if (!Objects.equals(oldValue, newValue)) {
|
||||
formSession.updateDraft(current -> current.withMetadataFieldValue(
|
||||
field.key(),
|
||||
Boolean.toString(newValue)));
|
||||
actionBar.updateState(formSession.mode(), formSession.isDirty());
|
||||
}
|
||||
});
|
||||
} else {
|
||||
configureReadOnlyControl(checkBox);
|
||||
}
|
||||
return checkBox;
|
||||
}
|
||||
|
||||
private Node createMetadataEnumField(
|
||||
PackerCodecConfigurationFieldDTO field,
|
||||
AssetContractDraft draft,
|
||||
boolean editing) {
|
||||
final ComboBox<String> comboBox = new ComboBox<>(FXCollections.observableArrayList(field.options()));
|
||||
comboBox.getStyleClass().add("assets-details-combo");
|
||||
comboBox.setMaxWidth(Double.MAX_VALUE);
|
||||
comboBox.getSelectionModel().select(currentMetadataValue(field, draft));
|
||||
if (editing) {
|
||||
comboBox.valueProperty().addListener((ignored, oldValue, newValue) -> {
|
||||
if (newValue != null && !Objects.equals(oldValue, newValue)) {
|
||||
formSession.updateDraft(current -> current.withMetadataFieldValue(
|
||||
field.key(),
|
||||
newValue));
|
||||
actionBar.updateState(formSession.mode(), formSession.isDirty());
|
||||
}
|
||||
});
|
||||
} else {
|
||||
configureReadOnlyControl(comboBox);
|
||||
}
|
||||
return comboBox;
|
||||
}
|
||||
|
||||
private Node createMetadataTextField(
|
||||
PackerCodecConfigurationFieldDTO field,
|
||||
AssetContractDraft draft,
|
||||
boolean editing) {
|
||||
final TextField textField = new TextField(currentMetadataValue(field, draft));
|
||||
textField.getStyleClass().add("assets-workspace-search");
|
||||
textField.setMaxWidth(Double.MAX_VALUE);
|
||||
HBox.setHgrow(textField, Priority.ALWAYS);
|
||||
if (editing) {
|
||||
textField.textProperty().addListener((ignored, oldValue, newValue) -> {
|
||||
if (!Objects.equals(oldValue, newValue)) {
|
||||
formSession.updateDraft(current -> current.withMetadataFieldValue(
|
||||
field.key(),
|
||||
Objects.requireNonNullElse(newValue, "")));
|
||||
actionBar.updateState(formSession.mode(), formSession.isDirty());
|
||||
}
|
||||
});
|
||||
} else {
|
||||
textField.setEditable(false);
|
||||
configureReadOnlyControl(textField);
|
||||
}
|
||||
return textField;
|
||||
}
|
||||
|
||||
private Node createCodecMetadataPane(Label title, VBox content) {
|
||||
final ScrollPane scrollPane = new ScrollPane(content);
|
||||
scrollPane.setFitToWidth(true);
|
||||
@ -320,6 +425,10 @@ public final class AssetDetailsContractControl extends VBox implements StudioCon
|
||||
return draft.codecFieldValue(draft.selectedCodec(), field.key(), field.value());
|
||||
}
|
||||
|
||||
private String currentMetadataValue(PackerCodecConfigurationFieldDTO field, AssetContractDraft draft) {
|
||||
return draft.metadataFieldValue(field.key(), field.value());
|
||||
}
|
||||
|
||||
private void configureReadOnlyControl(javafx.scene.control.Control control) {
|
||||
control.setMouseTransparent(true);
|
||||
control.setFocusTraversable(false);
|
||||
@ -344,7 +453,8 @@ public final class AssetDetailsContractControl extends VBox implements StudioCon
|
||||
draft.assetReference(),
|
||||
draft.preload(),
|
||||
draft.selectedCodec(),
|
||||
draft.codecFieldValues());
|
||||
draft.codecFieldValues(),
|
||||
draft.metadataFieldValues());
|
||||
try {
|
||||
final var response = Container.packer().workspaceService().updateAssetContract(request);
|
||||
if (response.success()) {
|
||||
|
||||
@ -16,6 +16,7 @@ public record AssetWorkspaceAssetDetails(
|
||||
OutputCodecCatalog outputCodec,
|
||||
List<OutputCodecCatalog> availableOutputCodecs,
|
||||
Map<OutputCodecCatalog, List<PackerCodecConfigurationFieldDTO>> codecConfigurationFieldsByCodec,
|
||||
List<PackerCodecConfigurationFieldDTO> metadataFields,
|
||||
Map<String, List<Path>> inputsByRole) {
|
||||
|
||||
public AssetWorkspaceAssetDetails {
|
||||
@ -25,6 +26,7 @@ public record AssetWorkspaceAssetDetails(
|
||||
outputCodec = Objects.requireNonNullElse(outputCodec, OutputCodecCatalog.UNKNOWN);
|
||||
availableOutputCodecs = List.copyOf(Objects.requireNonNull(availableOutputCodecs, "availableOutputCodecs"));
|
||||
codecConfigurationFieldsByCodec = Map.copyOf(Objects.requireNonNull(codecConfigurationFieldsByCodec, "codecConfigurationFieldsByCodec"));
|
||||
metadataFields = List.copyOf(Objects.requireNonNull(metadataFields, "metadataFields"));
|
||||
inputsByRole = Map.copyOf(Objects.requireNonNull(inputsByRole, "inputsByRole"));
|
||||
}
|
||||
}
|
||||
|
||||
@ -87,6 +87,7 @@ assets.badge.diagnostics=Diagnostics
|
||||
assets.section.summary=Summary
|
||||
assets.section.runtimeContract=Runtime Contract
|
||||
assets.subsection.codecConfiguration=Codec Configuration
|
||||
assets.subsection.metadata=Metadata
|
||||
assets.section.inputsPreview=Inputs / Preview
|
||||
assets.section.diagnostics=Diagnostics
|
||||
assets.section.actions=Actions
|
||||
@ -168,6 +169,7 @@ assets.details.empty=Create or add assets to this project to start using the Ass
|
||||
assets.details.ready=Selected asset: {0}\nState: {1}\nRoot: {2}
|
||||
assets.details.noSelection=Select an asset from the navigator once assets are available.
|
||||
assets.details.codecConfiguration.empty=This codec does not expose configuration fields yet.
|
||||
assets.details.metadata.empty=This asset does not expose metadata fields yet.
|
||||
assets.addWizard.title=Add Asset
|
||||
assets.addWizard.description=Create a registered asset root through a guided flow.
|
||||
assets.addWizard.step.root.title=Choose Asset Root
|
||||
|
||||
@ -748,6 +748,56 @@
|
||||
"message" : "Asset scan started",
|
||||
"severity" : "INFO",
|
||||
"sticky" : false
|
||||
}, {
|
||||
"source" : "Assets",
|
||||
"message" : "8 assets loaded",
|
||||
"severity" : "SUCCESS",
|
||||
"sticky" : false
|
||||
}, {
|
||||
"source" : "Assets",
|
||||
"message" : "Discovered asset: test",
|
||||
"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" : "Asset moved: bbb2 -> recovered/bbb2",
|
||||
@ -2448,54 +2498,4 @@
|
||||
"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: 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" : "Discovered asset: test",
|
||||
"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: bla",
|
||||
"severity" : "INFO",
|
||||
"sticky" : false
|
||||
}, {
|
||||
"source" : "Assets",
|
||||
"message" : "Discovered asset: one-more-atlas",
|
||||
"severity" : "INFO",
|
||||
"sticky" : false
|
||||
} ]
|
||||
Loading…
x
Reference in New Issue
Block a user