implements PR-022
This commit is contained in:
parent
a14a0b8b37
commit
165be76128
@ -0,0 +1,77 @@
|
|||||||
|
# PR-22 Delete Asset Action Confirmation and Fs-First Manifest Removal
|
||||||
|
|
||||||
|
Domain Owner: `docs/packer`
|
||||||
|
Cross-Domain Impact: `docs/studio`
|
||||||
|
|
||||||
|
## Briefing
|
||||||
|
|
||||||
|
The action capability contract introduced in `PR-20` needs its next real delivery beyond `REGISTER`.
|
||||||
|
|
||||||
|
`DELETE` must remove the asset from packer control without deleting the asset directory or its remaining files. The operation is filesystem-first: delete `asset.json`, update `index.json` when needed, and then apply a point snapshot update in memory.
|
||||||
|
|
||||||
|
Studio must require explicit confirmation before calling this write path.
|
||||||
|
|
||||||
|
## Objective
|
||||||
|
|
||||||
|
Deliver `AssetAction.DELETE` end to end with packer-owned capability resolution, Studio confirmation modal, filesystem-first manifest removal, and point runtime snapshot update.
|
||||||
|
|
||||||
|
## Dependencies
|
||||||
|
|
||||||
|
- [`./PR-20-asset-action-capabilities-and-register-first-delivery.md`](./PR-20-asset-action-capabilities-and-register-first-delivery.md)
|
||||||
|
- [`./PR-21-point-in-memory-snapshot-updates-after-write-commit.md`](./PR-21-point-in-memory-snapshot-updates-after-write-commit.md)
|
||||||
|
|
||||||
|
## Scope
|
||||||
|
|
||||||
|
- extend the public action contract with `DELETE`
|
||||||
|
- expose `DELETE` capability from the packer for assets that currently own `asset.json`
|
||||||
|
- add a packer write path that deletes only `asset.json`
|
||||||
|
- remove any registered entry from `index.json`
|
||||||
|
- keep the asset directory and its non-manifest files on disk
|
||||||
|
- patch the loaded runtime snapshot in memory after successful delete
|
||||||
|
- add a Studio modal that requires typing the asset name before confirming deletion
|
||||||
|
|
||||||
|
## Non-Goals
|
||||||
|
|
||||||
|
- no recursive directory deletion
|
||||||
|
- no deletion of companion files or arbitrary asset contents
|
||||||
|
- no frontend-local action capability rules
|
||||||
|
- no bulk delete
|
||||||
|
|
||||||
|
## Execution Method
|
||||||
|
|
||||||
|
1. Extend the action enum and packer API with `DELETE` and its write message/response.
|
||||||
|
2. Add packer capability resolution for `DELETE` based on `asset.json` presence, independent from declaration validity.
|
||||||
|
3. Implement `deleteAsset` in the packer write lane.
|
||||||
|
4. Make the write path:
|
||||||
|
- resolve the asset
|
||||||
|
- delete `asset.json`
|
||||||
|
- remove the registry entry when the asset is registered
|
||||||
|
- keep the asset directory and any remaining files untouched
|
||||||
|
- patch the in-memory snapshot by removing the asset from runtime view
|
||||||
|
5. Add a Studio confirmation modal that requires the user to type the asset name exactly.
|
||||||
|
6. On success, let Studio refresh and clear the current selection.
|
||||||
|
|
||||||
|
## Acceptance Criteria
|
||||||
|
|
||||||
|
- `DELETE` is exposed through the packer action capability contract
|
||||||
|
- Studio renders `DELETE` only from packer-provided capabilities
|
||||||
|
- Studio requires asset-name confirmation before executing `DELETE`
|
||||||
|
- `DELETE` removes only `asset.json`
|
||||||
|
- registered assets are also removed from `index.json`
|
||||||
|
- the asset directory and remaining files stay on disk
|
||||||
|
- the runtime snapshot is updated in memory without whole-project reload in the normal path
|
||||||
|
|
||||||
|
## Validation
|
||||||
|
|
||||||
|
- packer tests for `DELETE` capability visibility
|
||||||
|
- packer tests for deleting registered and unregistered assets
|
||||||
|
- packer tests proving directory contents remain on disk after delete
|
||||||
|
- Studio compile/test validation for the confirmation modal and action wiring
|
||||||
|
|
||||||
|
## Affected Artifacts
|
||||||
|
|
||||||
|
- `prometeu-packer/prometeu-packer-api/src/main/java/p/packer/**`
|
||||||
|
- `prometeu-packer/prometeu-packer-v1/src/main/java/p/packer/**`
|
||||||
|
- `prometeu-packer/prometeu-packer-v1/src/test/java/p/packer/**`
|
||||||
|
- `prometeu-studio/src/main/java/p/studio/**`
|
||||||
|
- `prometeu-studio/src/main/resources/**`
|
||||||
@ -81,6 +81,7 @@ The current production track for the standalone `prometeu-packer` project is:
|
|||||||
19. [`PR-19-api-surface-audit-model-separation-and-public-read-dtos.md`](./PR-19-api-surface-audit-model-separation-and-public-read-dtos.md)
|
19. [`PR-19-api-surface-audit-model-separation-and-public-read-dtos.md`](./PR-19-api-surface-audit-model-separation-and-public-read-dtos.md)
|
||||||
20. [`PR-20-asset-action-capabilities-and-register-first-delivery.md`](./PR-20-asset-action-capabilities-and-register-first-delivery.md)
|
20. [`PR-20-asset-action-capabilities-and-register-first-delivery.md`](./PR-20-asset-action-capabilities-and-register-first-delivery.md)
|
||||||
21. [`PR-21-point-in-memory-snapshot-updates-after-write-commit.md`](./PR-21-point-in-memory-snapshot-updates-after-write-commit.md)
|
21. [`PR-21-point-in-memory-snapshot-updates-after-write-commit.md`](./PR-21-point-in-memory-snapshot-updates-after-write-commit.md)
|
||||||
|
22. [`PR-22-delete-asset-action-confirmation-and-fs-first-manifest-removal.md`](./PR-22-delete-asset-action-confirmation-and-fs-first-manifest-removal.md)
|
||||||
|
|
||||||
Current wave discipline from `PR-11` onward:
|
Current wave discipline from `PR-11` onward:
|
||||||
|
|
||||||
@ -91,4 +92,4 @@ Current wave discipline from `PR-11` onward:
|
|||||||
|
|
||||||
Recommended dependency chain:
|
Recommended dependency chain:
|
||||||
|
|
||||||
`PR-01 -> PR-02 -> PR-03 -> PR-04 -> PR-05 -> PR-06 -> PR-07 -> PR-08 -> PR-09 -> PR-10 -> PR-11 -> PR-12 -> PR-13 -> PR-14 -> PR-15 -> PR-16 -> PR-17 -> PR-18 -> PR-19 -> PR-20 -> PR-21`
|
`PR-01 -> PR-02 -> PR-03 -> PR-04 -> PR-05 -> PR-06 -> PR-07 -> PR-08 -> PR-09 -> PR-10 -> PR-11 -> PR-12 -> PR-13 -> PR-14 -> PR-15 -> PR-16 -> PR-17 -> PR-18 -> PR-19 -> PR-20 -> PR-21 -> PR-22`
|
||||||
|
|||||||
@ -14,4 +14,6 @@ public interface PackerWorkspaceService {
|
|||||||
CreateAssetResult createAsset(CreateAssetRequest request);
|
CreateAssetResult createAsset(CreateAssetRequest request);
|
||||||
|
|
||||||
RegisterAssetResult registerAsset(RegisterAssetRequest request);
|
RegisterAssetResult registerAsset(RegisterAssetRequest request);
|
||||||
|
|
||||||
|
DeleteAssetResult deleteAsset(DeleteAssetRequest request);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,13 @@
|
|||||||
|
package p.packer.messages;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
public record DeleteAssetRequest(
|
||||||
|
PackerProjectContext project,
|
||||||
|
AssetReference assetReference) {
|
||||||
|
|
||||||
|
public DeleteAssetRequest {
|
||||||
|
Objects.requireNonNull(project, "project");
|
||||||
|
Objects.requireNonNull(assetReference, "assetReference");
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,25 @@
|
|||||||
|
package p.packer.messages;
|
||||||
|
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
public record DeleteAssetResult(
|
||||||
|
PackerOperationStatus status,
|
||||||
|
String summary,
|
||||||
|
Path assetRoot,
|
||||||
|
Path manifestPath) {
|
||||||
|
|
||||||
|
public DeleteAssetResult {
|
||||||
|
Objects.requireNonNull(status, "status");
|
||||||
|
summary = Objects.requireNonNull(summary, "summary").trim();
|
||||||
|
if (summary.isBlank()) {
|
||||||
|
throw new IllegalArgumentException("summary must not be blank");
|
||||||
|
}
|
||||||
|
if (assetRoot != null) {
|
||||||
|
assetRoot = assetRoot.toAbsolutePath().normalize();
|
||||||
|
}
|
||||||
|
if (manifestPath != null) {
|
||||||
|
manifestPath = manifestPath.toAbsolutePath().normalize();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,5 +1,6 @@
|
|||||||
package p.packer.messages.assets;
|
package p.packer.messages.assets;
|
||||||
|
|
||||||
public enum AssetAction {
|
public enum AssetAction {
|
||||||
REGISTER
|
REGISTER,
|
||||||
|
DELETE
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,20 @@
|
|||||||
|
package p.packer.models;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
public record PackerDeleteAssetEvaluation(
|
||||||
|
PackerResolvedAssetReference resolved,
|
||||||
|
List<PackerDiagnostic> diagnostics,
|
||||||
|
boolean canDelete,
|
||||||
|
String reason) {
|
||||||
|
|
||||||
|
public PackerDeleteAssetEvaluation {
|
||||||
|
Objects.requireNonNull(resolved, "resolved");
|
||||||
|
diagnostics = List.copyOf(Objects.requireNonNull(diagnostics, "diagnostics"));
|
||||||
|
reason = reason == null ? null : reason.trim();
|
||||||
|
if (reason != null && reason.isBlank()) {
|
||||||
|
reason = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -17,6 +17,8 @@ import p.packer.events.PackerEventSink;
|
|||||||
import p.packer.events.PackerProgress;
|
import p.packer.events.PackerProgress;
|
||||||
import p.packer.messages.CreateAssetRequest;
|
import p.packer.messages.CreateAssetRequest;
|
||||||
import p.packer.messages.CreateAssetResult;
|
import p.packer.messages.CreateAssetResult;
|
||||||
|
import p.packer.messages.DeleteAssetRequest;
|
||||||
|
import p.packer.messages.DeleteAssetResult;
|
||||||
import p.packer.messages.GetAssetDetailsRequest;
|
import p.packer.messages.GetAssetDetailsRequest;
|
||||||
import p.packer.messages.GetAssetDetailsResult;
|
import p.packer.messages.GetAssetDetailsResult;
|
||||||
import p.packer.messages.InitWorkspaceRequest;
|
import p.packer.messages.InitWorkspaceRequest;
|
||||||
@ -27,6 +29,7 @@ import p.packer.messages.RegisterAssetRequest;
|
|||||||
import p.packer.messages.RegisterAssetResult;
|
import p.packer.messages.RegisterAssetResult;
|
||||||
import p.packer.PackerWorkspaceService;
|
import p.packer.PackerWorkspaceService;
|
||||||
import p.packer.models.PackerAssetDeclarationParseResult;
|
import p.packer.models.PackerAssetDeclarationParseResult;
|
||||||
|
import p.packer.models.PackerDeleteAssetEvaluation;
|
||||||
import p.packer.models.PackerAssetIdentity;
|
import p.packer.models.PackerAssetIdentity;
|
||||||
import p.packer.models.PackerRegisterAssetEvaluation;
|
import p.packer.models.PackerRegisterAssetEvaluation;
|
||||||
import p.packer.models.PackerAssetSummary;
|
import p.packer.models.PackerAssetSummary;
|
||||||
@ -166,6 +169,14 @@ public final class FileSystemPackerWorkspaceService implements PackerWorkspaceSe
|
|||||||
return writeCoordinator.execute(project, () -> registerAssetInWriteLane(safeRequest, events));
|
return writeCoordinator.execute(project, () -> registerAssetInWriteLane(safeRequest, events));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DeleteAssetResult deleteAsset(DeleteAssetRequest request) {
|
||||||
|
final DeleteAssetRequest safeRequest = Objects.requireNonNull(request, "request");
|
||||||
|
final PackerProjectContext project = safeRequest.project();
|
||||||
|
final PackerOperationEventEmitter events = new PackerOperationEventEmitter(project, eventSink);
|
||||||
|
return writeCoordinator.execute(project, () -> deleteAssetInWriteLane(safeRequest, events));
|
||||||
|
}
|
||||||
|
|
||||||
private CreateAssetResult createAssetInWriteLane(
|
private CreateAssetResult createAssetInWriteLane(
|
||||||
CreateAssetRequest request,
|
CreateAssetRequest request,
|
||||||
PackerOperationEventEmitter events) {
|
PackerOperationEventEmitter events) {
|
||||||
@ -295,6 +306,50 @@ public final class FileSystemPackerWorkspaceService implements PackerWorkspaceSe
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private DeleteAssetResult deleteAssetInWriteLane(
|
||||||
|
DeleteAssetRequest request,
|
||||||
|
PackerOperationEventEmitter events) {
|
||||||
|
final PackerProjectContext project = request.project();
|
||||||
|
workspaceFoundation.initWorkspace(new InitWorkspaceRequest(project));
|
||||||
|
final PackerRuntimeSnapshot snapshot = runtimeRegistry.getOrLoad(project).snapshot();
|
||||||
|
final PackerDeleteAssetEvaluation evaluation = actionReadService.evaluateDelete(snapshot, project, request.assetReference());
|
||||||
|
final Path assetRoot = evaluation.resolved().assetRoot();
|
||||||
|
final Path manifestPath = assetRoot.resolve("asset.json");
|
||||||
|
final String relativeAssetRoot = relativeAssetRoot(project, assetRoot);
|
||||||
|
if (!evaluation.canDelete()) {
|
||||||
|
final String summary = Objects.requireNonNullElse(evaluation.reason(), "Asset cannot be deleted.");
|
||||||
|
return deleteFailureResult(events, summary, assetRoot, manifestPath, List.of(relativeAssetRoot));
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
final PackerRegistryState registry = workspaceFoundation.loadRegistry(project);
|
||||||
|
final PackerRegistryState updatedRegistry = removeRegistryEntry(registry, evaluation.resolved().registryEntry().orElse(null));
|
||||||
|
Files.deleteIfExists(manifestPath);
|
||||||
|
if (!updatedRegistry.equals(registry)) {
|
||||||
|
workspaceFoundation.saveRegistry(project, updatedRegistry);
|
||||||
|
}
|
||||||
|
runtimeRegistry.update(project, (currentSnapshot, generation) -> runtimePatchService.afterDeleteAsset(
|
||||||
|
currentSnapshot,
|
||||||
|
generation,
|
||||||
|
updatedRegistry,
|
||||||
|
assetRoot));
|
||||||
|
final DeleteAssetResult result = new DeleteAssetResult(
|
||||||
|
PackerOperationStatus.SUCCESS,
|
||||||
|
"Asset deleted: " + relativeAssetRoot,
|
||||||
|
assetRoot,
|
||||||
|
manifestPath);
|
||||||
|
events.emit(PackerEventKind.ACTION_APPLIED, result.summary(), List.of(relativeAssetRoot));
|
||||||
|
return result;
|
||||||
|
} catch (IOException exception) {
|
||||||
|
return deleteFailureResult(
|
||||||
|
events,
|
||||||
|
"Unable to delete asset: " + exception.getMessage(),
|
||||||
|
assetRoot,
|
||||||
|
manifestPath,
|
||||||
|
List.of(relativeAssetRoot));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private CreateAssetResult failureResult(
|
private CreateAssetResult failureResult(
|
||||||
PackerOperationEventEmitter events,
|
PackerOperationEventEmitter events,
|
||||||
String summary,
|
String summary,
|
||||||
@ -327,6 +382,21 @@ public final class FileSystemPackerWorkspaceService implements PackerWorkspaceSe
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private DeleteAssetResult deleteFailureResult(
|
||||||
|
PackerOperationEventEmitter events,
|
||||||
|
String summary,
|
||||||
|
Path assetRoot,
|
||||||
|
Path manifestPath,
|
||||||
|
List<String> affectedAssets) {
|
||||||
|
final DeleteAssetResult result = new DeleteAssetResult(
|
||||||
|
PackerOperationStatus.FAILED,
|
||||||
|
summary,
|
||||||
|
assetRoot,
|
||||||
|
manifestPath);
|
||||||
|
events.emit(PackerEventKind.ACTION_FAILED, result.summary(), affectedAssets);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
private void writeManifest(Path manifestPath, CreateAssetRequest request, String assetUuid) throws IOException {
|
private void writeManifest(Path manifestPath, CreateAssetRequest request, String assetUuid) throws IOException {
|
||||||
final Map<String, Object> manifest = new LinkedHashMap<>();
|
final Map<String, Object> manifest = new LinkedHashMap<>();
|
||||||
manifest.put("schema_version", 1);
|
manifest.put("schema_version", 1);
|
||||||
@ -420,4 +490,16 @@ public final class FileSystemPackerWorkspaceService implements PackerWorkspaceSe
|
|||||||
private String relativeAssetRoot(PackerProjectContext project, Path assetRoot) {
|
private String relativeAssetRoot(PackerProjectContext project, Path assetRoot) {
|
||||||
return PackerWorkspacePaths.relativeAssetRoot(project, assetRoot).replace('\\', '/');
|
return PackerWorkspacePaths.relativeAssetRoot(project, assetRoot).replace('\\', '/');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private PackerRegistryState removeRegistryEntry(
|
||||||
|
PackerRegistryState registry,
|
||||||
|
PackerRegistryEntry entry) {
|
||||||
|
if (entry == null) {
|
||||||
|
return registry;
|
||||||
|
}
|
||||||
|
final List<PackerRegistryEntry> updatedEntries = registry.assets().stream()
|
||||||
|
.filter(candidate -> candidate.assetId() != entry.assetId())
|
||||||
|
.toList();
|
||||||
|
return registry.withAssets(updatedEntries, registry.nextAssetId());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -12,6 +12,7 @@ import p.packer.messages.diagnostics.PackerDiagnosticSeverity;
|
|||||||
import p.packer.models.PackerAssetActionAvailability;
|
import p.packer.models.PackerAssetActionAvailability;
|
||||||
import p.packer.models.PackerAssetDeclaration;
|
import p.packer.models.PackerAssetDeclaration;
|
||||||
import p.packer.models.PackerAssetDeclarationParseResult;
|
import p.packer.models.PackerAssetDeclarationParseResult;
|
||||||
|
import p.packer.models.PackerDeleteAssetEvaluation;
|
||||||
import p.packer.models.PackerDiagnostic;
|
import p.packer.models.PackerDiagnostic;
|
||||||
import p.packer.models.PackerRegisterAssetEvaluation;
|
import p.packer.models.PackerRegisterAssetEvaluation;
|
||||||
import p.packer.models.PackerRegistryEntry;
|
import p.packer.models.PackerRegistryEntry;
|
||||||
@ -43,6 +44,7 @@ public final class PackerAssetActionReadService {
|
|||||||
final PackerProjectContext project = Objects.requireNonNull(request, "request").project();
|
final PackerProjectContext project = Objects.requireNonNull(request, "request").project();
|
||||||
final PackerRuntimeSnapshot snapshot = runtimeRegistry.getOrLoad(project).snapshot();
|
final PackerRuntimeSnapshot snapshot = runtimeRegistry.getOrLoad(project).snapshot();
|
||||||
final PackerRegisterAssetEvaluation registerEvaluation = evaluateRegister(snapshot, project, request.assetReference());
|
final PackerRegisterAssetEvaluation registerEvaluation = evaluateRegister(snapshot, project, request.assetReference());
|
||||||
|
final PackerDeleteAssetEvaluation deleteEvaluation = evaluateDelete(snapshot, project, request.assetReference());
|
||||||
final List<PackerAssetActionAvailability> actions = new ArrayList<>();
|
final List<PackerAssetActionAvailability> actions = new ArrayList<>();
|
||||||
if (registerEvaluation.resolved().registryEntry().isEmpty()) {
|
if (registerEvaluation.resolved().registryEntry().isEmpty()) {
|
||||||
actions.add(new PackerAssetActionAvailability(
|
actions.add(new PackerAssetActionAvailability(
|
||||||
@ -51,11 +53,18 @@ public final class PackerAssetActionReadService {
|
|||||||
true,
|
true,
|
||||||
registerEvaluation.reason()));
|
registerEvaluation.reason()));
|
||||||
}
|
}
|
||||||
|
if (deleteEvaluation.resolved().runtimeAsset().isPresent()) {
|
||||||
|
actions.add(new PackerAssetActionAvailability(
|
||||||
|
AssetAction.DELETE,
|
||||||
|
deleteEvaluation.canDelete(),
|
||||||
|
true,
|
||||||
|
deleteEvaluation.reason()));
|
||||||
|
}
|
||||||
|
|
||||||
final PackerOperationStatus status;
|
final PackerOperationStatus status;
|
||||||
if (registerEvaluation.resolved().runtimeAsset().isEmpty()) {
|
if (registerEvaluation.resolved().runtimeAsset().isEmpty() && deleteEvaluation.resolved().runtimeAsset().isEmpty()) {
|
||||||
status = PackerOperationStatus.FAILED;
|
status = PackerOperationStatus.FAILED;
|
||||||
} else if (registerEvaluation.diagnostics().stream().anyMatch(PackerDiagnostic::blocking)) {
|
} else if (combinedDiagnostics(registerEvaluation.diagnostics(), deleteEvaluation.diagnostics()).stream().anyMatch(PackerDiagnostic::blocking)) {
|
||||||
status = PackerOperationStatus.PARTIAL;
|
status = PackerOperationStatus.PARTIAL;
|
||||||
} else {
|
} else {
|
||||||
status = PackerOperationStatus.SUCCESS;
|
status = PackerOperationStatus.SUCCESS;
|
||||||
@ -64,7 +73,7 @@ public final class PackerAssetActionReadService {
|
|||||||
status,
|
status,
|
||||||
"Asset action capabilities resolved from runtime snapshot.",
|
"Asset action capabilities resolved from runtime snapshot.",
|
||||||
PackerReadMessageMapper.toAssetActionAvailabilityDTOs(actions),
|
PackerReadMessageMapper.toAssetActionAvailabilityDTOs(actions),
|
||||||
PackerReadMessageMapper.toDiagnosticDTOs(registerEvaluation.diagnostics()));
|
PackerReadMessageMapper.toDiagnosticDTOs(combinedDiagnostics(registerEvaluation.diagnostics(), deleteEvaluation.diagnostics())));
|
||||||
}
|
}
|
||||||
|
|
||||||
public PackerRegisterAssetEvaluation evaluateRegister(
|
public PackerRegisterAssetEvaluation evaluateRegister(
|
||||||
@ -120,6 +129,30 @@ public final class PackerAssetActionReadService {
|
|||||||
return new PackerRegisterAssetEvaluation(resolved, diagnostics, true, null);
|
return new PackerRegisterAssetEvaluation(resolved, diagnostics, true, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public PackerDeleteAssetEvaluation evaluateDelete(
|
||||||
|
PackerRuntimeSnapshot snapshot,
|
||||||
|
PackerProjectContext project,
|
||||||
|
AssetReference assetReference) {
|
||||||
|
final PackerResolvedAssetReference resolved = assetReferenceResolver.resolve(project, snapshot, assetReference);
|
||||||
|
final List<PackerDiagnostic> diagnostics = new ArrayList<>(resolved.diagnostics());
|
||||||
|
if (resolved.runtimeAsset().isEmpty()) {
|
||||||
|
if (resolved.registryEntry().isPresent()) {
|
||||||
|
diagnostics.add(new PackerDiagnostic(
|
||||||
|
PackerDiagnosticSeverity.ERROR,
|
||||||
|
PackerDiagnosticCategory.STRUCTURAL,
|
||||||
|
"asset.json was not found for the requested asset root.",
|
||||||
|
resolved.assetRoot().resolve("asset.json"),
|
||||||
|
true));
|
||||||
|
}
|
||||||
|
return new PackerDeleteAssetEvaluation(
|
||||||
|
resolved,
|
||||||
|
diagnostics,
|
||||||
|
false,
|
||||||
|
"asset.json was not found for the requested asset root.");
|
||||||
|
}
|
||||||
|
return new PackerDeleteAssetEvaluation(resolved, diagnostics, true, null);
|
||||||
|
}
|
||||||
|
|
||||||
private String firstBlockingReason(List<PackerDiagnostic> diagnostics, String fallback) {
|
private String firstBlockingReason(List<PackerDiagnostic> diagnostics, String fallback) {
|
||||||
return diagnostics.stream()
|
return diagnostics.stream()
|
||||||
.filter(PackerDiagnostic::blocking)
|
.filter(PackerDiagnostic::blocking)
|
||||||
@ -128,4 +161,12 @@ public final class PackerAssetActionReadService {
|
|||||||
.findFirst()
|
.findFirst()
|
||||||
.orElse(fallback);
|
.orElse(fallback);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private List<PackerDiagnostic> combinedDiagnostics(
|
||||||
|
List<PackerDiagnostic> left,
|
||||||
|
List<PackerDiagnostic> right) {
|
||||||
|
final List<PackerDiagnostic> combined = new ArrayList<>(left);
|
||||||
|
combined.addAll(right);
|
||||||
|
return combined;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -65,4 +65,15 @@ public final class PackerRuntimePatchService {
|
|||||||
}
|
}
|
||||||
return new PackerRuntimeSnapshot(generation, updatedRegistry, updatedAssets);
|
return new PackerRuntimeSnapshot(generation, updatedRegistry, updatedAssets);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public PackerRuntimeSnapshot afterDeleteAsset(
|
||||||
|
PackerRuntimeSnapshot snapshot,
|
||||||
|
long generation,
|
||||||
|
PackerRegistryState updatedRegistry,
|
||||||
|
Path assetRoot) {
|
||||||
|
final List<PackerRuntimeAsset> updatedAssets = snapshot.assets().stream()
|
||||||
|
.filter(asset -> !asset.assetRoot().equals(assetRoot.toAbsolutePath().normalize()))
|
||||||
|
.toList();
|
||||||
|
return new PackerRuntimeSnapshot(generation, updatedRegistry, updatedAssets);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -14,6 +14,8 @@ import p.packer.events.PackerEvent;
|
|||||||
import p.packer.events.PackerEventKind;
|
import p.packer.events.PackerEventKind;
|
||||||
import p.packer.messages.CreateAssetRequest;
|
import p.packer.messages.CreateAssetRequest;
|
||||||
import p.packer.messages.CreateAssetResult;
|
import p.packer.messages.CreateAssetResult;
|
||||||
|
import p.packer.messages.DeleteAssetRequest;
|
||||||
|
import p.packer.messages.DeleteAssetResult;
|
||||||
import p.packer.messages.GetAssetActionsRequest;
|
import p.packer.messages.GetAssetActionsRequest;
|
||||||
import p.packer.messages.GetAssetDetailsRequest;
|
import p.packer.messages.GetAssetDetailsRequest;
|
||||||
import p.packer.messages.ListAssetsRequest;
|
import p.packer.messages.ListAssetsRequest;
|
||||||
@ -183,11 +185,15 @@ final class FileSystemPackerWorkspaceServiceTest {
|
|||||||
AssetReference.forRelativeAssetRoot("orphans/ui_sounds")));
|
AssetReference.forRelativeAssetRoot("orphans/ui_sounds")));
|
||||||
|
|
||||||
assertEquals(PackerOperationStatus.SUCCESS, result.status());
|
assertEquals(PackerOperationStatus.SUCCESS, result.status());
|
||||||
assertEquals(1, result.actions().size());
|
assertEquals(2, result.actions().size());
|
||||||
assertEquals(AssetAction.REGISTER, result.actions().getFirst().action());
|
assertEquals(AssetAction.REGISTER, result.actions().get(0).action());
|
||||||
assertTrue(result.actions().getFirst().visible());
|
assertTrue(result.actions().get(0).visible());
|
||||||
assertTrue(result.actions().getFirst().enabled());
|
assertTrue(result.actions().get(0).enabled());
|
||||||
assertNull(result.actions().getFirst().reason());
|
assertNull(result.actions().get(0).reason());
|
||||||
|
assertEquals(AssetAction.DELETE, result.actions().get(1).action());
|
||||||
|
assertTrue(result.actions().get(1).visible());
|
||||||
|
assertTrue(result.actions().get(1).enabled());
|
||||||
|
assertNull(result.actions().get(1).reason());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -200,7 +206,9 @@ final class FileSystemPackerWorkspaceServiceTest {
|
|||||||
AssetReference.forAssetId(1)));
|
AssetReference.forAssetId(1)));
|
||||||
|
|
||||||
assertEquals(PackerOperationStatus.SUCCESS, result.status());
|
assertEquals(PackerOperationStatus.SUCCESS, result.status());
|
||||||
assertTrue(result.actions().isEmpty());
|
assertEquals(1, result.actions().size());
|
||||||
|
assertEquals(AssetAction.DELETE, result.actions().getFirst().action());
|
||||||
|
assertTrue(result.actions().getFirst().enabled());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -246,7 +254,8 @@ final class FileSystemPackerWorkspaceServiceTest {
|
|||||||
final var actionsResult = service.getAssetActions(new GetAssetActionsRequest(
|
final var actionsResult = service.getAssetActions(new GetAssetActionsRequest(
|
||||||
project(projectRoot),
|
project(projectRoot),
|
||||||
registerResult.assetReference()));
|
registerResult.assetReference()));
|
||||||
assertTrue(actionsResult.actions().isEmpty());
|
assertEquals(1, actionsResult.actions().size());
|
||||||
|
assertEquals(AssetAction.DELETE, actionsResult.actions().getFirst().action());
|
||||||
assertEquals(1, loader.loadCount());
|
assertEquals(1, loader.loadCount());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -260,9 +269,12 @@ final class FileSystemPackerWorkspaceServiceTest {
|
|||||||
AssetReference.forRelativeAssetRoot("bad")));
|
AssetReference.forRelativeAssetRoot("bad")));
|
||||||
|
|
||||||
assertEquals(PackerOperationStatus.PARTIAL, result.status());
|
assertEquals(PackerOperationStatus.PARTIAL, result.status());
|
||||||
assertEquals(1, result.actions().size());
|
assertEquals(2, result.actions().size());
|
||||||
assertFalse(result.actions().getFirst().enabled());
|
assertEquals(AssetAction.REGISTER, result.actions().get(0).action());
|
||||||
assertNotNull(result.actions().getFirst().reason());
|
assertFalse(result.actions().get(0).enabled());
|
||||||
|
assertNotNull(result.actions().get(0).reason());
|
||||||
|
assertEquals(AssetAction.DELETE, result.actions().get(1).action());
|
||||||
|
assertTrue(result.actions().get(1).enabled());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -322,9 +334,12 @@ final class FileSystemPackerWorkspaceServiceTest {
|
|||||||
final var actions = service.getAssetActions(new GetAssetActionsRequest(
|
final var actions = service.getAssetActions(new GetAssetActionsRequest(
|
||||||
project(projectRoot),
|
project(projectRoot),
|
||||||
AssetReference.forRelativeAssetRoot("orphans/ui_clone")));
|
AssetReference.forRelativeAssetRoot("orphans/ui_clone")));
|
||||||
assertEquals(1, actions.actions().size());
|
assertEquals(2, actions.actions().size());
|
||||||
assertFalse(actions.actions().getFirst().enabled());
|
assertEquals(AssetAction.REGISTER, actions.actions().get(0).action());
|
||||||
assertNotNull(actions.actions().getFirst().reason());
|
assertFalse(actions.actions().get(0).enabled());
|
||||||
|
assertNotNull(actions.actions().get(0).reason());
|
||||||
|
assertEquals(AssetAction.DELETE, actions.actions().get(1).action());
|
||||||
|
assertTrue(actions.actions().get(1).enabled());
|
||||||
|
|
||||||
final RegisterAssetResult registerResult = service.registerAsset(new RegisterAssetRequest(
|
final RegisterAssetResult registerResult = service.registerAsset(new RegisterAssetRequest(
|
||||||
project(projectRoot),
|
project(projectRoot),
|
||||||
@ -333,6 +348,55 @@ final class FileSystemPackerWorkspaceServiceTest {
|
|||||||
assertNull(registerResult.assetReference());
|
assertNull(registerResult.assetReference());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void deletesRegisteredAssetJsonWithoutDeletingDirectoryContents() throws Exception {
|
||||||
|
final Path projectRoot = tempDir.resolve("delete-registered");
|
||||||
|
final FileSystemPackerWorkspaceService service = service();
|
||||||
|
|
||||||
|
final CreateAssetResult createResult = service.createAsset(new CreateAssetRequest(
|
||||||
|
project(projectRoot),
|
||||||
|
"ui/delete-me",
|
||||||
|
"delete_me",
|
||||||
|
AssetFamilyCatalog.IMAGE_BANK,
|
||||||
|
OutputFormatCatalog.TILES_INDEXED_V1,
|
||||||
|
OutputCodecCatalog.NONE,
|
||||||
|
false));
|
||||||
|
final Path assetRoot = projectRoot.resolve("assets/ui/delete-me");
|
||||||
|
Files.writeString(assetRoot.resolve("atlas.png"), "fixture");
|
||||||
|
|
||||||
|
final DeleteAssetResult deleteResult = service.deleteAsset(new DeleteAssetRequest(
|
||||||
|
project(projectRoot),
|
||||||
|
createResult.assetReference()));
|
||||||
|
|
||||||
|
assertEquals(PackerOperationStatus.SUCCESS, deleteResult.status());
|
||||||
|
assertTrue(Files.isDirectory(assetRoot));
|
||||||
|
assertFalse(Files.exists(assetRoot.resolve("asset.json")));
|
||||||
|
assertTrue(Files.isRegularFile(assetRoot.resolve("atlas.png")));
|
||||||
|
|
||||||
|
final var listResult = service.listAssets(new ListAssetsRequest(project(projectRoot)));
|
||||||
|
assertTrue(listResult.assets().isEmpty());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void deletesUnregisteredAssetJsonAndRemovesItFromSnapshot() throws Exception {
|
||||||
|
final Path projectRoot = copyFixture("workspaces/orphan-valid", tempDir.resolve("delete-unregistered"));
|
||||||
|
final FileSystemPackerWorkspaceService service = service();
|
||||||
|
final Path assetRoot = projectRoot.resolve("assets/orphans/ui_sounds");
|
||||||
|
Files.writeString(assetRoot.resolve("notes.txt"), "keep me");
|
||||||
|
|
||||||
|
final DeleteAssetResult deleteResult = service.deleteAsset(new DeleteAssetRequest(
|
||||||
|
project(projectRoot),
|
||||||
|
AssetReference.forRelativeAssetRoot("orphans/ui_sounds")));
|
||||||
|
|
||||||
|
assertEquals(PackerOperationStatus.SUCCESS, deleteResult.status());
|
||||||
|
assertTrue(Files.isDirectory(assetRoot));
|
||||||
|
assertFalse(Files.exists(assetRoot.resolve("asset.json")));
|
||||||
|
assertTrue(Files.isRegularFile(assetRoot.resolve("notes.txt")));
|
||||||
|
|
||||||
|
final var listResult = service.listAssets(new ListAssetsRequest(project(projectRoot)));
|
||||||
|
assertTrue(listResult.assets().isEmpty());
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void rejectsUnsupportedFormatForSelectedFamily() {
|
void rejectsUnsupportedFormatForSelectedFamily() {
|
||||||
final Path projectRoot = tempDir.resolve("unsupported");
|
final Path projectRoot = tempDir.resolve("unsupported");
|
||||||
|
|||||||
@ -102,10 +102,15 @@ public enum I18n {
|
|||||||
ASSETS_SECTION_ACTIONS("assets.section.actions"),
|
ASSETS_SECTION_ACTIONS("assets.section.actions"),
|
||||||
ASSETS_ACTIONS_EMPTY("assets.actions.empty"),
|
ASSETS_ACTIONS_EMPTY("assets.actions.empty"),
|
||||||
ASSETS_ACTION_REGISTER("assets.action.register"),
|
ASSETS_ACTION_REGISTER("assets.action.register"),
|
||||||
|
ASSETS_ACTION_DELETE("assets.action.delete"),
|
||||||
ASSETS_ACTION_INCLUDE_IN_BUILD("assets.action.includeInBuild"),
|
ASSETS_ACTION_INCLUDE_IN_BUILD("assets.action.includeInBuild"),
|
||||||
ASSETS_ACTION_EXCLUDE_FROM_BUILD("assets.action.excludeFromBuild"),
|
ASSETS_ACTION_EXCLUDE_FROM_BUILD("assets.action.excludeFromBuild"),
|
||||||
ASSETS_ACTION_RELOCATE("assets.action.relocate"),
|
ASSETS_ACTION_RELOCATE("assets.action.relocate"),
|
||||||
ASSETS_ACTION_REMOVE("assets.action.remove"),
|
ASSETS_DELETE_DIALOG_TITLE("assets.deleteDialog.title"),
|
||||||
|
ASSETS_DELETE_DIALOG_DESCRIPTION("assets.deleteDialog.description"),
|
||||||
|
ASSETS_DELETE_DIALOG_PROMPT("assets.deleteDialog.prompt"),
|
||||||
|
ASSETS_DELETE_DIALOG_NOTE("assets.deleteDialog.note"),
|
||||||
|
ASSETS_DELETE_DIALOG_CONFIRM("assets.deleteDialog.confirm"),
|
||||||
ASSETS_MUTATION_PREVIEW_TITLE("assets.mutation.previewTitle"),
|
ASSETS_MUTATION_PREVIEW_TITLE("assets.mutation.previewTitle"),
|
||||||
ASSETS_MUTATION_SECTION_CHANGES("assets.mutation.section.changes"),
|
ASSETS_MUTATION_SECTION_CHANGES("assets.mutation.section.changes"),
|
||||||
ASSETS_MUTATION_SECTION_AFFECTED_ASSET("assets.mutation.section.affectedAsset"),
|
ASSETS_MUTATION_SECTION_AFFECTED_ASSET("assets.mutation.section.affectedAsset"),
|
||||||
|
|||||||
@ -11,6 +11,8 @@ import javafx.scene.layout.VBox;
|
|||||||
import p.packer.messages.AssetReference;
|
import p.packer.messages.AssetReference;
|
||||||
import p.packer.dtos.PackerAssetActionAvailabilityDTO;
|
import p.packer.dtos.PackerAssetActionAvailabilityDTO;
|
||||||
import p.packer.dtos.PackerAssetDetailsDTO;
|
import p.packer.dtos.PackerAssetDetailsDTO;
|
||||||
|
import p.packer.messages.DeleteAssetRequest;
|
||||||
|
import p.packer.messages.DeleteAssetResult;
|
||||||
import p.packer.messages.GetAssetDetailsRequest;
|
import p.packer.messages.GetAssetDetailsRequest;
|
||||||
import p.packer.messages.GetAssetActionsRequest;
|
import p.packer.messages.GetAssetActionsRequest;
|
||||||
import p.packer.messages.RegisterAssetRequest;
|
import p.packer.messages.RegisterAssetRequest;
|
||||||
@ -29,6 +31,7 @@ import p.studio.workspaces.assets.messages.AssetWorkspaceDetailsViewState;
|
|||||||
import p.studio.workspaces.assets.messages.events.StudioAssetsDetailsViewStateChangedEvent;
|
import p.studio.workspaces.assets.messages.events.StudioAssetsDetailsViewStateChangedEvent;
|
||||||
import p.studio.workspaces.assets.messages.events.StudioAssetsRefreshRequestedEvent;
|
import p.studio.workspaces.assets.messages.events.StudioAssetsRefreshRequestedEvent;
|
||||||
import p.studio.workspaces.assets.messages.events.StudioAssetsWorkspaceSelectionRequestedEvent;
|
import p.studio.workspaces.assets.messages.events.StudioAssetsWorkspaceSelectionRequestedEvent;
|
||||||
|
import p.studio.workspaces.assets.wizards.DeleteAssetDialog;
|
||||||
import p.studio.workspaces.framework.StudioEventAware;
|
import p.studio.workspaces.framework.StudioEventAware;
|
||||||
import p.studio.workspaces.framework.StudioEventBindings;
|
import p.studio.workspaces.framework.StudioEventBindings;
|
||||||
|
|
||||||
@ -302,12 +305,32 @@ public final class AssetDetailsControl extends VBox implements StudioEventAware
|
|||||||
if (actionRunning || viewState.selectedAssetReference() == null) {
|
if (actionRunning || viewState.selectedAssetReference() == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
switch (action.action()) {
|
||||||
|
case REGISTER -> {
|
||||||
|
actionRunning = true;
|
||||||
|
actionFeedbackMessage = null;
|
||||||
|
renderActions();
|
||||||
|
Container.backgroundTasks().submit(() -> registerSelectedAsset(viewState.selectedAssetReference()));
|
||||||
|
}
|
||||||
|
case DELETE -> confirmDeleteAction();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void confirmDeleteAction() {
|
||||||
|
if (viewState.selectedAssetDetails() == null || getScene() == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final boolean confirmed = DeleteAssetDialog.showAndWait(
|
||||||
|
getScene().getWindow(),
|
||||||
|
viewState.selectedAssetDetails().summary().assetName());
|
||||||
|
if (!confirmed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
actionRunning = true;
|
actionRunning = true;
|
||||||
actionFeedbackMessage = null;
|
actionFeedbackMessage = null;
|
||||||
renderActions();
|
renderActions();
|
||||||
switch (action.action()) {
|
final AssetReference assetReference = viewState.selectedAssetReference();
|
||||||
case REGISTER -> Container.backgroundTasks().submit(() -> registerSelectedAsset(viewState.selectedAssetReference()));
|
Container.backgroundTasks().submit(() -> deleteSelectedAsset(assetReference));
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void registerSelectedAsset(AssetReference assetReference) {
|
private void registerSelectedAsset(AssetReference assetReference) {
|
||||||
@ -341,6 +364,36 @@ public final class AssetDetailsControl extends VBox implements StudioEventAware
|
|||||||
renderActions();
|
renderActions();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void deleteSelectedAsset(AssetReference assetReference) {
|
||||||
|
try {
|
||||||
|
final DeleteAssetResult result = Container.packer().workspaceService().deleteAsset(new DeleteAssetRequest(
|
||||||
|
projectReference.toPackerProjectContext(),
|
||||||
|
assetReference));
|
||||||
|
Platform.runLater(() -> applyDeleteResult(result));
|
||||||
|
} catch (RuntimeException exception) {
|
||||||
|
Platform.runLater(() -> applyDeleteFailure(exception));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void applyDeleteResult(DeleteAssetResult result) {
|
||||||
|
actionRunning = false;
|
||||||
|
if (result.status() == p.packer.messages.PackerOperationStatus.SUCCESS) {
|
||||||
|
actionFeedbackMessage = null;
|
||||||
|
workspaceBus.publish(new StudioAssetsRefreshRequestedEvent());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
actionFeedbackMessage = Objects.requireNonNullElse(result.summary(), "Unable to delete asset.");
|
||||||
|
renderActions();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void applyDeleteFailure(RuntimeException exception) {
|
||||||
|
actionRunning = false;
|
||||||
|
actionFeedbackMessage = exception.getMessage() == null || exception.getMessage().isBlank()
|
||||||
|
? "Unable to delete asset."
|
||||||
|
: exception.getMessage();
|
||||||
|
renderActions();
|
||||||
|
}
|
||||||
|
|
||||||
private AssetWorkspaceAssetDetails mapDetails(
|
private AssetWorkspaceAssetDetails mapDetails(
|
||||||
PackerAssetDetailsDTO details,
|
PackerAssetDetailsDTO details,
|
||||||
java.util.List<PackerAssetActionAvailabilityDTO> actions) {
|
java.util.List<PackerAssetActionAvailabilityDTO> actions) {
|
||||||
|
|||||||
@ -108,6 +108,7 @@ public final class AssetDetailsUiSupport {
|
|||||||
public static String actionLabel(AssetAction action) {
|
public static String actionLabel(AssetAction action) {
|
||||||
return switch (action) {
|
return switch (action) {
|
||||||
case REGISTER -> Container.i18n().text(I18n.ASSETS_ACTION_REGISTER);
|
case REGISTER -> Container.i18n().text(I18n.ASSETS_ACTION_REGISTER);
|
||||||
|
case DELETE -> Container.i18n().text(I18n.ASSETS_ACTION_DELETE);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -0,0 +1,84 @@
|
|||||||
|
package p.studio.workspaces.assets.wizards;
|
||||||
|
|
||||||
|
import javafx.beans.binding.Bindings;
|
||||||
|
import javafx.geometry.Insets;
|
||||||
|
import javafx.geometry.Pos;
|
||||||
|
import javafx.scene.Scene;
|
||||||
|
import javafx.scene.control.Button;
|
||||||
|
import javafx.scene.control.Label;
|
||||||
|
import javafx.scene.control.TextField;
|
||||||
|
import javafx.scene.layout.HBox;
|
||||||
|
import javafx.scene.layout.Priority;
|
||||||
|
import javafx.scene.layout.VBox;
|
||||||
|
import javafx.stage.Modality;
|
||||||
|
import javafx.stage.Stage;
|
||||||
|
import javafx.stage.Window;
|
||||||
|
import p.studio.Container;
|
||||||
|
import p.studio.utilities.i18n.I18n;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
|
||||||
|
public final class DeleteAssetDialog {
|
||||||
|
private final Stage stage;
|
||||||
|
private final AtomicReference<Boolean> result = new AtomicReference<>(false);
|
||||||
|
private final String assetName;
|
||||||
|
private final TextField confirmationField = new TextField();
|
||||||
|
|
||||||
|
private DeleteAssetDialog(Window owner, String assetName) {
|
||||||
|
this.assetName = Objects.requireNonNull(assetName, "assetName").trim();
|
||||||
|
this.stage = new Stage();
|
||||||
|
stage.initOwner(owner);
|
||||||
|
stage.initModality(Modality.WINDOW_MODAL);
|
||||||
|
stage.setTitle(Container.i18n().text(I18n.ASSETS_DELETE_DIALOG_TITLE));
|
||||||
|
stage.setScene(new Scene(buildRoot(), 520, 240));
|
||||||
|
stage.getScene().getStylesheets().add(Container.theme().getDefaultTheme());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean showAndWait(Window owner, String assetName) {
|
||||||
|
final DeleteAssetDialog dialog = new DeleteAssetDialog(owner, assetName);
|
||||||
|
dialog.stage.showAndWait();
|
||||||
|
return Optional.ofNullable(dialog.result.get()).orElse(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private VBox buildRoot() {
|
||||||
|
final Label title = new Label(Container.i18n().text(I18n.ASSETS_DELETE_DIALOG_TITLE));
|
||||||
|
title.getStyleClass().add("studio-launcher-section-title");
|
||||||
|
|
||||||
|
final Label description = new Label(Container.i18n().format(I18n.ASSETS_DELETE_DIALOG_DESCRIPTION, assetName));
|
||||||
|
description.getStyleClass().add("studio-launcher-subtitle");
|
||||||
|
description.setWrapText(true);
|
||||||
|
|
||||||
|
confirmationField.setPromptText(Container.i18n().text(I18n.ASSETS_DELETE_DIALOG_PROMPT));
|
||||||
|
confirmationField.setMaxWidth(Double.MAX_VALUE);
|
||||||
|
|
||||||
|
final Label note = new Label(Container.i18n().text(I18n.ASSETS_DELETE_DIALOG_NOTE));
|
||||||
|
note.getStyleClass().add("studio-launcher-feedback");
|
||||||
|
note.setWrapText(true);
|
||||||
|
|
||||||
|
final Button confirmButton = new Button();
|
||||||
|
confirmButton.textProperty().bind(Container.i18n().bind(I18n.ASSETS_DELETE_DIALOG_CONFIRM));
|
||||||
|
confirmButton.getStyleClass().addAll("studio-button", "studio-button-danger");
|
||||||
|
confirmButton.disableProperty().bind(Bindings.createBooleanBinding(
|
||||||
|
() -> !assetName.equals(confirmationField.getText().trim()),
|
||||||
|
confirmationField.textProperty()));
|
||||||
|
confirmButton.setOnAction(ignored -> {
|
||||||
|
result.set(true);
|
||||||
|
stage.close();
|
||||||
|
});
|
||||||
|
|
||||||
|
final Button cancelButton = new Button();
|
||||||
|
cancelButton.textProperty().bind(Container.i18n().bind(I18n.WIZARD_CANCEL));
|
||||||
|
cancelButton.getStyleClass().addAll("studio-button", "studio-button-cancel");
|
||||||
|
cancelButton.setOnAction(ignored -> stage.close());
|
||||||
|
|
||||||
|
final HBox actions = new HBox(12, confirmButton, cancelButton);
|
||||||
|
actions.setAlignment(Pos.CENTER_RIGHT);
|
||||||
|
|
||||||
|
final VBox root = new VBox(16, title, description, confirmationField, note, actions);
|
||||||
|
root.setPadding(new Insets(24));
|
||||||
|
VBox.setVgrow(confirmationField, Priority.NEVER);
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -92,6 +92,12 @@ assets.section.diagnostics=Diagnostics
|
|||||||
assets.section.actions=Actions
|
assets.section.actions=Actions
|
||||||
assets.actions.empty=No actions available for this asset.
|
assets.actions.empty=No actions available for this asset.
|
||||||
assets.action.register=Register
|
assets.action.register=Register
|
||||||
|
assets.action.delete=Delete
|
||||||
|
assets.deleteDialog.title=Delete Asset
|
||||||
|
assets.deleteDialog.description=Type the asset name below to confirm deletion of {0}.
|
||||||
|
assets.deleteDialog.prompt=Type the asset name exactly
|
||||||
|
assets.deleteDialog.note=Only asset.json is deleted. The asset directory and its remaining files stay on disk.
|
||||||
|
assets.deleteDialog.confirm=Delete
|
||||||
assets.action.includeInBuild=Include In Build
|
assets.action.includeInBuild=Include In Build
|
||||||
assets.action.excludeFromBuild=Exclude From Build
|
assets.action.excludeFromBuild=Exclude From Build
|
||||||
assets.action.relocate=Relocate
|
assets.action.relocate=Relocate
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user