added move action into asset workspace
This commit is contained in:
parent
29a2398b1d
commit
bf4dc17469
@ -0,0 +1,101 @@
|
||||
# PR-23 Move Asset Action Wizard and Fs-First Relocation
|
||||
|
||||
Domain Owner: `docs/packer`
|
||||
Cross-Domain Impact: `docs/studio`
|
||||
|
||||
## Briefing
|
||||
|
||||
The action capability track already delivers `REGISTER` and `DELETE`.
|
||||
|
||||
The next operational action is `MOVE`, which must let the user relocate an asset root inside the project's `assets/` tree and optionally rename the asset directory in the same flow.
|
||||
|
||||
Studio should own the wizard and the interaction flow. The packer should own the validation semantics, the filesystem move, the registry update, and the point snapshot patch after durable commit.
|
||||
|
||||
## Objective
|
||||
|
||||
Deliver `AssetAction.MOVE` end to end with a Studio relocation wizard, packer-owned constraints, filesystem-first directory move, and minimal in-memory 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)
|
||||
- [`./PR-22-delete-asset-action-confirmation-and-fs-first-manifest-removal.md`](./PR-22-delete-asset-action-confirmation-and-fs-first-manifest-removal.md)
|
||||
|
||||
## Scope
|
||||
|
||||
- extend the public action contract and write messages for `MOVE`
|
||||
- expose `MOVE` capability from the packer
|
||||
- add a Studio wizard that collects:
|
||||
- destination parent directory
|
||||
- destination directory name
|
||||
- derived target root inside `assets/`
|
||||
- add a confirmation step before execution
|
||||
- add an execution/waiting step with spinner while the move is running
|
||||
- allow the wizard to work as both relocate and rename of the asset directory
|
||||
- enforce packer constraints that:
|
||||
- the target must stay inside the project's `assets/`
|
||||
- the target root must not already contain `asset.json`
|
||||
- perform the actual move in the filesystem inside the packer write lane
|
||||
- update `index.json` and patch the runtime snapshot minimally after commit
|
||||
|
||||
## Non-Goals
|
||||
|
||||
- no move outside the project's `assets/`
|
||||
- no directory merge behavior
|
||||
- no recursive validation of non-manifest file contents
|
||||
- no batch move
|
||||
- no frontend-local move semantics
|
||||
|
||||
## Execution Method
|
||||
|
||||
1. Extend the action contract with `MOVE` and add write request/response messages for asset relocation.
|
||||
2. Add packer capability resolution for `MOVE`.
|
||||
3. Build a Studio wizard dedicated to move/rename, with:
|
||||
- current asset root display
|
||||
- destination parent picker constrained to `assets/`
|
||||
- destination directory name field
|
||||
- derived target root preview
|
||||
- validation feedback
|
||||
- confirmation step before submit
|
||||
- waiting state with spinner after submit until the write result is known
|
||||
4. Enforce these constraints in the packer:
|
||||
- the target root must remain under `assets/`
|
||||
- the target root must not already contain `asset.json`
|
||||
5. Execute the move in the packer write lane by:
|
||||
- moving the asset directory in the filesystem
|
||||
- updating the registry entry root when the asset is registered
|
||||
- preserving `asset_uuid`
|
||||
6. Patch the runtime snapshot in memory after the move, without whole-project reload in the normal path.
|
||||
7. Let Studio refresh and reselect the moved asset after success.
|
||||
8. Keep the wizard open in waiting state while the move is running, then close only after success; on failure, leave the wizard recoverable with feedback.
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
- `MOVE` is exposed through the packer action capability contract
|
||||
- Studio opens a wizard for `MOVE`
|
||||
- the wizard has an explicit confirmation step before execution
|
||||
- the wizard enters a waiting state with spinner while the move is in flight
|
||||
- the wizard lets the user relocate and/or rename the asset directory
|
||||
- the move target cannot be outside the project's `assets/`
|
||||
- the move target cannot already contain `asset.json`
|
||||
- the packer performs the filesystem move
|
||||
- `index.json` is updated when needed
|
||||
- the runtime snapshot is patched minimally after success
|
||||
- Studio refreshes and keeps the moved asset selected
|
||||
|
||||
## Validation
|
||||
|
||||
- packer tests for move capability visibility
|
||||
- packer tests for successful relocate and successful rename
|
||||
- packer tests for blockers when target is outside `assets/`
|
||||
- packer tests for blockers when target already contains `asset.json`
|
||||
- packer tests proving snapshot patching after move
|
||||
- Studio smoke validation for wizard confirmation flow, waiting state, and post-move reselection
|
||||
|
||||
## 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/**`
|
||||
@ -82,6 +82,7 @@ The current production track for the standalone `prometeu-packer` project is:
|
||||
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)
|
||||
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)
|
||||
23. [`PR-23-move-asset-action-wizard-and-fs-first-relocation.md`](./PR-23-move-asset-action-wizard-and-fs-first-relocation.md)
|
||||
|
||||
Current wave discipline from `PR-11` onward:
|
||||
|
||||
@ -92,4 +93,4 @@ Current wave discipline from `PR-11` onward:
|
||||
|
||||
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-22`
|
||||
`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 -> PR-23`
|
||||
|
||||
@ -15,5 +15,7 @@ public interface PackerWorkspaceService {
|
||||
|
||||
RegisterAssetResult registerAsset(RegisterAssetRequest request);
|
||||
|
||||
MoveAssetResult moveAsset(MoveAssetRequest request);
|
||||
|
||||
DeleteAssetResult deleteAsset(DeleteAssetRequest request);
|
||||
}
|
||||
|
||||
@ -49,4 +49,15 @@ public record AssetReference(String value) {
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (!(o instanceof AssetReference(String v1))) return false;
|
||||
return value.equals(v1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return value.hashCode();
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,18 @@
|
||||
package p.packer.messages;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
public record MoveAssetRequest(
|
||||
PackerProjectContext project,
|
||||
AssetReference assetReference,
|
||||
String targetRoot) {
|
||||
|
||||
public MoveAssetRequest {
|
||||
Objects.requireNonNull(project, "project");
|
||||
Objects.requireNonNull(assetReference, "assetReference");
|
||||
targetRoot = Objects.requireNonNull(targetRoot, "targetRoot").trim();
|
||||
if (targetRoot.isBlank()) {
|
||||
throw new IllegalArgumentException("targetRoot must not be blank");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,26 @@
|
||||
package p.packer.messages;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.Objects;
|
||||
|
||||
public record MoveAssetResult(
|
||||
PackerOperationStatus status,
|
||||
String summary,
|
||||
AssetReference assetReference,
|
||||
Path assetRoot,
|
||||
Path manifestPath) {
|
||||
|
||||
public MoveAssetResult {
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -2,5 +2,6 @@ package p.packer.messages.assets;
|
||||
|
||||
public enum AssetAction {
|
||||
REGISTER,
|
||||
MOVE,
|
||||
DELETE
|
||||
}
|
||||
|
||||
@ -0,0 +1,28 @@
|
||||
package p.packer.models;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
|
||||
public record PackerMoveAssetEvaluation(
|
||||
PackerResolvedAssetReference resolved,
|
||||
List<PackerDiagnostic> diagnostics,
|
||||
boolean canMove,
|
||||
String reason,
|
||||
Optional<String> targetRelativeRoot,
|
||||
Optional<Path> targetRoot,
|
||||
Optional<Path> targetManifestPath) {
|
||||
|
||||
public PackerMoveAssetEvaluation {
|
||||
Objects.requireNonNull(resolved, "resolved");
|
||||
diagnostics = List.copyOf(Objects.requireNonNull(diagnostics, "diagnostics"));
|
||||
reason = reason == null ? null : reason.trim();
|
||||
if (reason != null && reason.isBlank()) {
|
||||
reason = null;
|
||||
}
|
||||
targetRelativeRoot = Objects.requireNonNull(targetRelativeRoot, "targetRelativeRoot");
|
||||
targetRoot = Objects.requireNonNull(targetRoot, "targetRoot");
|
||||
targetManifestPath = Objects.requireNonNull(targetManifestPath, "targetManifestPath");
|
||||
}
|
||||
}
|
||||
@ -7,6 +7,8 @@ import p.packer.messages.assets.AssetFamilyCatalog;
|
||||
import p.packer.messages.AssetReference;
|
||||
import p.packer.messages.GetAssetActionsRequest;
|
||||
import p.packer.messages.GetAssetActionsResult;
|
||||
import p.packer.messages.MoveAssetRequest;
|
||||
import p.packer.messages.MoveAssetResult;
|
||||
import p.packer.messages.assets.OutputFormatCatalog;
|
||||
import p.packer.messages.assets.PackerAssetState;
|
||||
import p.packer.messages.assets.PackerBuildParticipation;
|
||||
@ -31,6 +33,7 @@ import p.packer.PackerWorkspaceService;
|
||||
import p.packer.models.PackerAssetDeclarationParseResult;
|
||||
import p.packer.models.PackerDeleteAssetEvaluation;
|
||||
import p.packer.models.PackerAssetIdentity;
|
||||
import p.packer.models.PackerMoveAssetEvaluation;
|
||||
import p.packer.models.PackerRegisterAssetEvaluation;
|
||||
import p.packer.models.PackerAssetSummary;
|
||||
import p.packer.models.PackerDiagnostic;
|
||||
@ -171,6 +174,14 @@ public final class FileSystemPackerWorkspaceService implements PackerWorkspaceSe
|
||||
return writeCoordinator.execute(project, () -> registerAssetInWriteLane(safeRequest, events));
|
||||
}
|
||||
|
||||
@Override
|
||||
public MoveAssetResult moveAsset(MoveAssetRequest request) {
|
||||
final MoveAssetRequest safeRequest = Objects.requireNonNull(request, "request");
|
||||
final PackerProjectContext project = safeRequest.project();
|
||||
final PackerOperationEventEmitter events = new PackerOperationEventEmitter(project, eventSink);
|
||||
return writeCoordinator.execute(project, () -> moveAssetInWriteLane(safeRequest, events));
|
||||
}
|
||||
|
||||
@Override
|
||||
public DeleteAssetResult deleteAsset(DeleteAssetRequest request) {
|
||||
final DeleteAssetRequest safeRequest = Objects.requireNonNull(request, "request");
|
||||
@ -352,6 +363,69 @@ public final class FileSystemPackerWorkspaceService implements PackerWorkspaceSe
|
||||
}
|
||||
}
|
||||
|
||||
private MoveAssetResult moveAssetInWriteLane(
|
||||
MoveAssetRequest request,
|
||||
PackerOperationEventEmitter events) {
|
||||
final PackerProjectContext project = request.project();
|
||||
workspaceFoundation.initWorkspace(new InitWorkspaceRequest(project));
|
||||
final PackerRuntimeSnapshot snapshot = runtimeRegistry.getOrLoad(project).snapshot();
|
||||
final PackerMoveAssetEvaluation evaluation = actionReadService.evaluateMove(snapshot, project, request.assetReference(), request.targetRoot());
|
||||
if (!evaluation.canMove()) {
|
||||
return moveFailureResult(
|
||||
events,
|
||||
Objects.requireNonNullElse(evaluation.reason(), "Asset cannot be moved."),
|
||||
evaluation.resolved().assetRoot(),
|
||||
evaluation.resolved().assetRoot().resolve("asset.json"),
|
||||
List.of(relativeAssetRoot(project, evaluation.resolved().assetRoot())));
|
||||
}
|
||||
|
||||
final Path sourceRoot = evaluation.resolved().assetRoot();
|
||||
final Path targetRoot = evaluation.targetRoot().orElseThrow();
|
||||
final Path targetManifestPath = evaluation.targetManifestPath().orElseThrow();
|
||||
final String sourceRelativeRoot = relativeAssetRoot(project, sourceRoot);
|
||||
final String targetRelativeRoot = evaluation.targetRelativeRoot().orElseThrow();
|
||||
try {
|
||||
Files.createDirectories(targetRoot.getParent());
|
||||
Files.move(sourceRoot, targetRoot);
|
||||
|
||||
final PackerRegistryState registry = workspaceFoundation.loadRegistry(project);
|
||||
final Optional<PackerRegistryEntry> updatedEntry = evaluation.resolved().registryEntry()
|
||||
.map(entry -> new PackerRegistryEntry(entry.assetId(), entry.assetUuid(), targetRelativeRoot, entry.includedInBuild()));
|
||||
final PackerRegistryState updatedRegistry = updatedEntry
|
||||
.map(entry -> replaceRegistryEntryRoot(registry, entry))
|
||||
.orElse(registry);
|
||||
if (!updatedRegistry.equals(registry)) {
|
||||
workspaceFoundation.saveRegistry(project, updatedRegistry);
|
||||
}
|
||||
runtimeRegistry.update(project, (currentSnapshot, generation) -> runtimePatchService.afterMoveAsset(
|
||||
currentSnapshot,
|
||||
generation,
|
||||
updatedRegistry,
|
||||
updatedEntry,
|
||||
sourceRoot,
|
||||
targetRoot,
|
||||
targetManifestPath));
|
||||
final AssetReference canonicalReference = updatedEntry.isPresent()
|
||||
? AssetReference.forAssetId(updatedEntry.get().assetId())
|
||||
: AssetReference.forRelativeAssetRoot(targetRelativeRoot);
|
||||
final MoveAssetResult result = new MoveAssetResult(
|
||||
PackerOperationStatus.SUCCESS,
|
||||
"Asset moved: " + sourceRelativeRoot + " -> " + targetRelativeRoot,
|
||||
canonicalReference,
|
||||
targetRoot,
|
||||
targetManifestPath);
|
||||
events.emit(PackerEventKind.ACTION_APPLIED, result.summary(), List.of(sourceRelativeRoot, targetRelativeRoot));
|
||||
return result;
|
||||
} catch (IOException exception) {
|
||||
return moveFailureResult(
|
||||
events,
|
||||
"Unable to move asset: " + exception.getMessage(),
|
||||
sourceRoot,
|
||||
sourceRoot.resolve("asset.json"),
|
||||
List.of(sourceRelativeRoot, targetRelativeRoot));
|
||||
}
|
||||
}
|
||||
|
||||
private CreateAssetResult failureResult(
|
||||
PackerOperationEventEmitter events,
|
||||
String summary,
|
||||
@ -399,6 +473,22 @@ public final class FileSystemPackerWorkspaceService implements PackerWorkspaceSe
|
||||
return result;
|
||||
}
|
||||
|
||||
private MoveAssetResult moveFailureResult(
|
||||
PackerOperationEventEmitter events,
|
||||
String summary,
|
||||
Path assetRoot,
|
||||
Path manifestPath,
|
||||
List<String> affectedAssets) {
|
||||
final MoveAssetResult result = new MoveAssetResult(
|
||||
PackerOperationStatus.FAILED,
|
||||
summary,
|
||||
null,
|
||||
assetRoot,
|
||||
manifestPath);
|
||||
events.emit(PackerEventKind.ACTION_FAILED, result.summary(), affectedAssets);
|
||||
return result;
|
||||
}
|
||||
|
||||
private void writeManifest(Path manifestPath, CreateAssetRequest request, String assetUuid) throws IOException {
|
||||
final Map<String, Object> manifest = new LinkedHashMap<>();
|
||||
manifest.put("schema_version", 1);
|
||||
@ -504,4 +594,13 @@ public final class FileSystemPackerWorkspaceService implements PackerWorkspaceSe
|
||||
.toList();
|
||||
return registry.withAssets(updatedEntries, registry.nextAssetId());
|
||||
}
|
||||
|
||||
private PackerRegistryState replaceRegistryEntryRoot(
|
||||
PackerRegistryState registry,
|
||||
PackerRegistryEntry updatedEntry) {
|
||||
final List<PackerRegistryEntry> updatedEntries = registry.assets().stream()
|
||||
.map(candidate -> candidate.assetId() == updatedEntry.assetId() ? updatedEntry : candidate)
|
||||
.toList();
|
||||
return registry.withAssets(updatedEntries, registry.nextAssetId());
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
package p.packer.services;
|
||||
|
||||
import p.packer.dtos.PackerAssetActionAvailabilityDTO;
|
||||
import p.packer.messages.AssetReference;
|
||||
import p.packer.messages.GetAssetActionsRequest;
|
||||
import p.packer.messages.GetAssetActionsResult;
|
||||
@ -14,6 +13,7 @@ import p.packer.models.PackerAssetDeclaration;
|
||||
import p.packer.models.PackerAssetDeclarationParseResult;
|
||||
import p.packer.models.PackerDeleteAssetEvaluation;
|
||||
import p.packer.models.PackerDiagnostic;
|
||||
import p.packer.models.PackerMoveAssetEvaluation;
|
||||
import p.packer.models.PackerRegisterAssetEvaluation;
|
||||
import p.packer.models.PackerRegistryEntry;
|
||||
import p.packer.models.PackerResolvedAssetReference;
|
||||
@ -44,6 +44,7 @@ public final class PackerAssetActionReadService {
|
||||
final PackerProjectContext project = Objects.requireNonNull(request, "request").project();
|
||||
final PackerRuntimeSnapshot snapshot = runtimeRegistry.getOrLoad(project).snapshot();
|
||||
final PackerRegisterAssetEvaluation registerEvaluation = evaluateRegister(snapshot, project, request.assetReference());
|
||||
final PackerMoveAssetEvaluation moveEvaluation = evaluateMove(snapshot, project, request.assetReference(), null);
|
||||
final PackerDeleteAssetEvaluation deleteEvaluation = evaluateDelete(snapshot, project, request.assetReference());
|
||||
final List<PackerAssetActionAvailability> actions = new ArrayList<>();
|
||||
if (registerEvaluation.resolved().registryEntry().isEmpty()) {
|
||||
@ -53,6 +54,13 @@ public final class PackerAssetActionReadService {
|
||||
true,
|
||||
registerEvaluation.reason()));
|
||||
}
|
||||
if (moveEvaluation.resolved().runtimeAsset().isPresent()) {
|
||||
actions.add(new PackerAssetActionAvailability(
|
||||
AssetAction.MOVE,
|
||||
moveEvaluation.canMove(),
|
||||
true,
|
||||
moveEvaluation.reason()));
|
||||
}
|
||||
if (deleteEvaluation.resolved().runtimeAsset().isPresent()) {
|
||||
actions.add(new PackerAssetActionAvailability(
|
||||
AssetAction.DELETE,
|
||||
@ -62,9 +70,12 @@ public final class PackerAssetActionReadService {
|
||||
}
|
||||
|
||||
final PackerOperationStatus status;
|
||||
if (registerEvaluation.resolved().runtimeAsset().isEmpty() && deleteEvaluation.resolved().runtimeAsset().isEmpty()) {
|
||||
if (registerEvaluation.resolved().runtimeAsset().isEmpty()
|
||||
&& moveEvaluation.resolved().runtimeAsset().isEmpty()
|
||||
&& deleteEvaluation.resolved().runtimeAsset().isEmpty()) {
|
||||
status = PackerOperationStatus.FAILED;
|
||||
} else if (combinedDiagnostics(registerEvaluation.diagnostics(), deleteEvaluation.diagnostics()).stream().anyMatch(PackerDiagnostic::blocking)) {
|
||||
} else if (combinedDiagnostics(registerEvaluation.diagnostics(), moveEvaluation.diagnostics(), deleteEvaluation.diagnostics())
|
||||
.stream().anyMatch(PackerDiagnostic::blocking)) {
|
||||
status = PackerOperationStatus.PARTIAL;
|
||||
} else {
|
||||
status = PackerOperationStatus.SUCCESS;
|
||||
@ -73,7 +84,10 @@ public final class PackerAssetActionReadService {
|
||||
status,
|
||||
"Asset action capabilities resolved from runtime snapshot.",
|
||||
PackerReadMessageMapper.toAssetActionAvailabilityDTOs(actions),
|
||||
PackerReadMessageMapper.toDiagnosticDTOs(combinedDiagnostics(registerEvaluation.diagnostics(), deleteEvaluation.diagnostics())));
|
||||
PackerReadMessageMapper.toDiagnosticDTOs(combinedDiagnostics(
|
||||
registerEvaluation.diagnostics(),
|
||||
moveEvaluation.diagnostics(),
|
||||
deleteEvaluation.diagnostics())));
|
||||
}
|
||||
|
||||
public PackerRegisterAssetEvaluation evaluateRegister(
|
||||
@ -153,6 +167,98 @@ public final class PackerAssetActionReadService {
|
||||
return new PackerDeleteAssetEvaluation(resolved, diagnostics, true, null);
|
||||
}
|
||||
|
||||
public PackerMoveAssetEvaluation evaluateMove(
|
||||
PackerRuntimeSnapshot snapshot,
|
||||
PackerProjectContext project,
|
||||
AssetReference assetReference,
|
||||
String requestedTargetRoot) {
|
||||
final PackerResolvedAssetReference resolved = assetReferenceResolver.resolve(project, snapshot, assetReference);
|
||||
final List<PackerDiagnostic> diagnostics = new ArrayList<>(resolved.diagnostics());
|
||||
if (resolved.runtimeAsset().isEmpty()) {
|
||||
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 PackerMoveAssetEvaluation(
|
||||
resolved,
|
||||
diagnostics,
|
||||
false,
|
||||
"asset.json was not found for the requested asset root.",
|
||||
Optional.empty(),
|
||||
Optional.empty(),
|
||||
Optional.empty());
|
||||
}
|
||||
|
||||
if (requestedTargetRoot == null) {
|
||||
return new PackerMoveAssetEvaluation(
|
||||
resolved,
|
||||
diagnostics,
|
||||
true,
|
||||
null,
|
||||
Optional.empty(),
|
||||
Optional.empty(),
|
||||
Optional.empty());
|
||||
}
|
||||
|
||||
final String normalizedTargetRoot = normalizeRelativeAssetRoot(requestedTargetRoot);
|
||||
if (normalizedTargetRoot == null) {
|
||||
return new PackerMoveAssetEvaluation(
|
||||
resolved,
|
||||
diagnostics,
|
||||
false,
|
||||
"Target root must stay inside assets/ and use a non-blank relative path.",
|
||||
Optional.empty(),
|
||||
Optional.empty(),
|
||||
Optional.empty());
|
||||
}
|
||||
|
||||
final Path targetRoot = PackerWorkspacePaths.assetRoot(project, normalizedTargetRoot);
|
||||
if (!targetRoot.startsWith(PackerWorkspacePaths.assetsRoot(project))) {
|
||||
return new PackerMoveAssetEvaluation(
|
||||
resolved,
|
||||
diagnostics,
|
||||
false,
|
||||
"Target root must stay inside assets/.",
|
||||
Optional.of(normalizedTargetRoot),
|
||||
Optional.of(targetRoot),
|
||||
Optional.of(targetRoot.resolve("asset.json")));
|
||||
}
|
||||
|
||||
if (targetRoot.equals(resolved.assetRoot())) {
|
||||
return new PackerMoveAssetEvaluation(
|
||||
resolved,
|
||||
diagnostics,
|
||||
false,
|
||||
"Target root must differ from the current asset root.",
|
||||
Optional.of(normalizedTargetRoot),
|
||||
Optional.of(targetRoot),
|
||||
Optional.of(targetRoot.resolve("asset.json")));
|
||||
}
|
||||
|
||||
final Path targetManifestPath = targetRoot.resolve("asset.json");
|
||||
if (java.nio.file.Files.isRegularFile(targetManifestPath)) {
|
||||
return new PackerMoveAssetEvaluation(
|
||||
resolved,
|
||||
diagnostics,
|
||||
false,
|
||||
"Target root already contains asset.json.",
|
||||
Optional.of(normalizedTargetRoot),
|
||||
Optional.of(targetRoot),
|
||||
Optional.of(targetManifestPath));
|
||||
}
|
||||
|
||||
return new PackerMoveAssetEvaluation(
|
||||
resolved,
|
||||
diagnostics,
|
||||
true,
|
||||
null,
|
||||
Optional.of(normalizedTargetRoot),
|
||||
Optional.of(targetRoot),
|
||||
Optional.of(targetManifestPath));
|
||||
}
|
||||
|
||||
private String firstBlockingReason(List<PackerDiagnostic> diagnostics, String fallback) {
|
||||
return diagnostics.stream()
|
||||
.filter(PackerDiagnostic::blocking)
|
||||
@ -162,11 +268,25 @@ public final class PackerAssetActionReadService {
|
||||
.orElse(fallback);
|
||||
}
|
||||
|
||||
private List<PackerDiagnostic> combinedDiagnostics(
|
||||
List<PackerDiagnostic> left,
|
||||
List<PackerDiagnostic> right) {
|
||||
final List<PackerDiagnostic> combined = new ArrayList<>(left);
|
||||
combined.addAll(right);
|
||||
@SafeVarargs
|
||||
private List<PackerDiagnostic> combinedDiagnostics(List<PackerDiagnostic>... groups) {
|
||||
final List<PackerDiagnostic> combined = new ArrayList<>();
|
||||
for (List<PackerDiagnostic> group : groups) {
|
||||
combined.addAll(group);
|
||||
}
|
||||
return combined;
|
||||
}
|
||||
|
||||
private String normalizeRelativeAssetRoot(String candidate) {
|
||||
final String raw = Objects.requireNonNullElse(candidate, "").trim().replace('\\', '/');
|
||||
if (raw.isBlank()) {
|
||||
return null;
|
||||
}
|
||||
final Path normalized = Path.of(raw).normalize();
|
||||
if (normalized.isAbsolute() || normalized.startsWith("..")) {
|
||||
return null;
|
||||
}
|
||||
final String value = normalized.toString().replace('\\', '/');
|
||||
return value.isBlank() ? null : value;
|
||||
}
|
||||
}
|
||||
|
||||
@ -76,4 +76,34 @@ public final class PackerRuntimePatchService {
|
||||
.toList();
|
||||
return new PackerRuntimeSnapshot(generation, updatedRegistry, updatedAssets);
|
||||
}
|
||||
|
||||
public PackerRuntimeSnapshot afterMoveAsset(
|
||||
PackerRuntimeSnapshot snapshot,
|
||||
long generation,
|
||||
PackerRegistryState updatedRegistry,
|
||||
java.util.Optional<PackerRegistryEntry> updatedRegistryEntry,
|
||||
Path sourceRoot,
|
||||
Path targetRoot,
|
||||
Path targetManifestPath) {
|
||||
final PackerAssetDeclarationParseResult parsed = declarationParser.parse(targetManifestPath);
|
||||
final List<PackerRuntimeAsset> updatedAssets = new ArrayList<>();
|
||||
boolean patched = false;
|
||||
for (PackerRuntimeAsset asset : snapshot.assets()) {
|
||||
if (asset.assetRoot().equals(sourceRoot.toAbsolutePath().normalize())) {
|
||||
updatedAssets.add(new PackerRuntimeAsset(
|
||||
targetRoot,
|
||||
targetManifestPath,
|
||||
updatedRegistryEntry,
|
||||
parsed));
|
||||
patched = true;
|
||||
} else {
|
||||
updatedAssets.add(asset);
|
||||
}
|
||||
}
|
||||
if (!patched) {
|
||||
throw new IllegalStateException("Unable to patch runtime snapshot for moved asset: " + sourceRoot);
|
||||
}
|
||||
updatedAssets.sort(Comparator.comparing(asset -> asset.assetRoot().toString(), String.CASE_INSENSITIVE_ORDER));
|
||||
return new PackerRuntimeSnapshot(generation, updatedRegistry, updatedAssets);
|
||||
}
|
||||
}
|
||||
|
||||
@ -19,6 +19,8 @@ import p.packer.messages.DeleteAssetResult;
|
||||
import p.packer.messages.GetAssetActionsRequest;
|
||||
import p.packer.messages.GetAssetDetailsRequest;
|
||||
import p.packer.messages.ListAssetsRequest;
|
||||
import p.packer.messages.MoveAssetRequest;
|
||||
import p.packer.messages.MoveAssetResult;
|
||||
import p.packer.messages.RegisterAssetRequest;
|
||||
import p.packer.messages.RegisterAssetResult;
|
||||
import p.packer.messages.assets.AssetAction;
|
||||
@ -201,15 +203,16 @@ final class FileSystemPackerWorkspaceServiceTest {
|
||||
AssetReference.forRelativeAssetRoot("orphans/ui_sounds")));
|
||||
|
||||
assertEquals(PackerOperationStatus.SUCCESS, result.status());
|
||||
assertEquals(2, result.actions().size());
|
||||
assertEquals(3, result.actions().size());
|
||||
assertEquals(AssetAction.REGISTER, result.actions().get(0).action());
|
||||
assertTrue(result.actions().get(0).visible());
|
||||
assertTrue(result.actions().get(0).enabled());
|
||||
assertNull(result.actions().get(0).reason());
|
||||
assertEquals(AssetAction.DELETE, result.actions().get(1).action());
|
||||
assertEquals(AssetAction.MOVE, result.actions().get(1).action());
|
||||
assertTrue(result.actions().get(1).visible());
|
||||
assertTrue(result.actions().get(1).enabled());
|
||||
assertNull(result.actions().get(1).reason());
|
||||
assertEquals(AssetAction.DELETE, result.actions().get(2).action());
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -222,9 +225,11 @@ final class FileSystemPackerWorkspaceServiceTest {
|
||||
AssetReference.forAssetId(1)));
|
||||
|
||||
assertEquals(PackerOperationStatus.SUCCESS, result.status());
|
||||
assertEquals(1, result.actions().size());
|
||||
assertEquals(AssetAction.DELETE, result.actions().getFirst().action());
|
||||
assertTrue(result.actions().getFirst().enabled());
|
||||
assertEquals(2, result.actions().size());
|
||||
assertEquals(AssetAction.MOVE, result.actions().get(0).action());
|
||||
assertTrue(result.actions().get(0).enabled());
|
||||
assertEquals(AssetAction.DELETE, result.actions().get(1).action());
|
||||
assertTrue(result.actions().get(1).enabled());
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -270,8 +275,9 @@ final class FileSystemPackerWorkspaceServiceTest {
|
||||
final var actionsResult = service.getAssetActions(new GetAssetActionsRequest(
|
||||
project(projectRoot),
|
||||
registerResult.assetReference()));
|
||||
assertEquals(1, actionsResult.actions().size());
|
||||
assertEquals(AssetAction.DELETE, actionsResult.actions().getFirst().action());
|
||||
assertEquals(2, actionsResult.actions().size());
|
||||
assertEquals(AssetAction.MOVE, actionsResult.actions().get(0).action());
|
||||
assertEquals(AssetAction.DELETE, actionsResult.actions().get(1).action());
|
||||
assertEquals(1, loader.loadCount());
|
||||
}
|
||||
|
||||
@ -285,12 +291,13 @@ final class FileSystemPackerWorkspaceServiceTest {
|
||||
AssetReference.forRelativeAssetRoot("bad")));
|
||||
|
||||
assertEquals(PackerOperationStatus.PARTIAL, result.status());
|
||||
assertEquals(2, result.actions().size());
|
||||
assertEquals(3, result.actions().size());
|
||||
assertEquals(AssetAction.REGISTER, result.actions().get(0).action());
|
||||
assertFalse(result.actions().get(0).enabled());
|
||||
assertNotNull(result.actions().get(0).reason());
|
||||
assertEquals(AssetAction.DELETE, result.actions().get(1).action());
|
||||
assertEquals(AssetAction.MOVE, result.actions().get(1).action());
|
||||
assertTrue(result.actions().get(1).enabled());
|
||||
assertEquals(AssetAction.DELETE, result.actions().get(2).action());
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -350,12 +357,13 @@ final class FileSystemPackerWorkspaceServiceTest {
|
||||
final var actions = service.getAssetActions(new GetAssetActionsRequest(
|
||||
project(projectRoot),
|
||||
AssetReference.forRelativeAssetRoot("orphans/ui_clone")));
|
||||
assertEquals(2, actions.actions().size());
|
||||
assertEquals(3, actions.actions().size());
|
||||
assertEquals(AssetAction.REGISTER, actions.actions().get(0).action());
|
||||
assertFalse(actions.actions().get(0).enabled());
|
||||
assertNotNull(actions.actions().get(0).reason());
|
||||
assertEquals(AssetAction.DELETE, actions.actions().get(1).action());
|
||||
assertEquals(AssetAction.MOVE, actions.actions().get(1).action());
|
||||
assertTrue(actions.actions().get(1).enabled());
|
||||
assertEquals(AssetAction.DELETE, actions.actions().get(2).action());
|
||||
|
||||
final RegisterAssetResult registerResult = service.registerAsset(new RegisterAssetRequest(
|
||||
project(projectRoot),
|
||||
@ -393,6 +401,126 @@ final class FileSystemPackerWorkspaceServiceTest {
|
||||
assertTrue(listResult.assets().isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
void movesRegisteredAssetAndKeepsRegisteredIdentity() throws Exception {
|
||||
final Path projectRoot = tempDir.resolve("move-registered");
|
||||
final FileSystemPackerWorkspaceService service = service();
|
||||
final CreateAssetResult createResult = service.createAsset(new CreateAssetRequest(
|
||||
project(projectRoot),
|
||||
"ui/source-atlas",
|
||||
"source_atlas",
|
||||
AssetFamilyCatalog.IMAGE_BANK,
|
||||
OutputFormatCatalog.TILES_INDEXED_V1,
|
||||
OutputCodecCatalog.NONE,
|
||||
true));
|
||||
Files.writeString(projectRoot.resolve("assets/ui/source-atlas/atlas.png"), "fixture");
|
||||
|
||||
final MoveAssetResult moveResult = service.moveAsset(new MoveAssetRequest(
|
||||
project(projectRoot),
|
||||
createResult.assetReference(),
|
||||
"ui/renamed-atlas"));
|
||||
|
||||
assertEquals(PackerOperationStatus.SUCCESS, moveResult.status());
|
||||
assertEquals(createResult.assetReference(), moveResult.assetReference());
|
||||
assertFalse(Files.exists(projectRoot.resolve("assets/ui/source-atlas/asset.json")));
|
||||
assertTrue(Files.isRegularFile(projectRoot.resolve("assets/ui/renamed-atlas/asset.json")));
|
||||
assertTrue(Files.isRegularFile(projectRoot.resolve("assets/ui/renamed-atlas/atlas.png")));
|
||||
|
||||
final var details = service.getAssetDetails(new GetAssetDetailsRequest(project(projectRoot), createResult.assetReference()));
|
||||
assertEquals(PackerOperationStatus.SUCCESS, details.status());
|
||||
assertEquals(PackerAssetState.REGISTERED, details.details().summary().state());
|
||||
assertEquals(projectRoot.resolve("assets/ui/renamed-atlas").toAbsolutePath().normalize(), details.details().summary().identity().assetRoot());
|
||||
}
|
||||
|
||||
@Test
|
||||
void movesUnregisteredAssetAndReturnsNewRootReference() throws Exception {
|
||||
final Path projectRoot = copyFixture("workspaces/orphan-valid", tempDir.resolve("move-unregistered"));
|
||||
final FileSystemPackerWorkspaceService service = service();
|
||||
Files.writeString(projectRoot.resolve("assets/orphans/ui_sounds/readme.txt"), "keep");
|
||||
|
||||
final MoveAssetResult moveResult = service.moveAsset(new MoveAssetRequest(
|
||||
project(projectRoot),
|
||||
AssetReference.forRelativeAssetRoot("orphans/ui_sounds"),
|
||||
"recovered/ui_sounds_renamed"));
|
||||
|
||||
assertEquals(PackerOperationStatus.SUCCESS, moveResult.status());
|
||||
assertEquals(AssetReference.forRelativeAssetRoot("recovered/ui_sounds_renamed"), moveResult.assetReference());
|
||||
assertTrue(Files.isRegularFile(projectRoot.resolve("assets/recovered/ui_sounds_renamed/asset.json")));
|
||||
assertTrue(Files.isRegularFile(projectRoot.resolve("assets/recovered/ui_sounds_renamed/readme.txt")));
|
||||
|
||||
final var details = service.getAssetDetails(new GetAssetDetailsRequest(project(projectRoot), moveResult.assetReference()));
|
||||
assertEquals(PackerOperationStatus.SUCCESS, details.status());
|
||||
assertEquals(PackerAssetState.UNREGISTERED, details.details().summary().state());
|
||||
assertEquals(projectRoot.resolve("assets/recovered/ui_sounds_renamed").toAbsolutePath().normalize(), details.details().summary().identity().assetRoot());
|
||||
}
|
||||
|
||||
@Test
|
||||
void blocksMoveOutsideAssetsRoot() throws Exception {
|
||||
final Path projectRoot = copyFixture("workspaces/orphan-valid", tempDir.resolve("move-outside"));
|
||||
final FileSystemPackerWorkspaceService service = service();
|
||||
|
||||
final MoveAssetResult moveResult = service.moveAsset(new MoveAssetRequest(
|
||||
project(projectRoot),
|
||||
AssetReference.forRelativeAssetRoot("orphans/ui_sounds"),
|
||||
"../outside/ui_sounds"));
|
||||
|
||||
assertEquals(PackerOperationStatus.FAILED, moveResult.status());
|
||||
assertNull(moveResult.assetReference());
|
||||
}
|
||||
|
||||
@Test
|
||||
void blocksMoveWhenTargetAlreadyContainsAssetManifest() throws Exception {
|
||||
final Path projectRoot = tempDir.resolve("move-target-asset");
|
||||
final FileSystemPackerWorkspaceService service = service();
|
||||
final CreateAssetResult source = service.createAsset(new CreateAssetRequest(
|
||||
project(projectRoot),
|
||||
"ui/source",
|
||||
"source",
|
||||
AssetFamilyCatalog.IMAGE_BANK,
|
||||
OutputFormatCatalog.TILES_INDEXED_V1,
|
||||
OutputCodecCatalog.NONE,
|
||||
false));
|
||||
final CreateAssetResult target = service.createAsset(new CreateAssetRequest(
|
||||
project(projectRoot),
|
||||
"ui/target",
|
||||
"target",
|
||||
AssetFamilyCatalog.IMAGE_BANK,
|
||||
OutputFormatCatalog.TILES_INDEXED_V1,
|
||||
OutputCodecCatalog.NONE,
|
||||
false));
|
||||
|
||||
final MoveAssetResult moveResult = service.moveAsset(new MoveAssetRequest(
|
||||
project(projectRoot),
|
||||
source.assetReference(),
|
||||
"ui/target"));
|
||||
|
||||
assertEquals(PackerOperationStatus.FAILED, moveResult.status());
|
||||
assertNull(moveResult.assetReference());
|
||||
assertNotNull(target.assetReference());
|
||||
}
|
||||
|
||||
@Test
|
||||
void moveAssetPatchesLoadedSnapshotWithoutWholeProjectReload() throws Exception {
|
||||
final Path projectRoot = copyFixture("workspaces/orphan-valid", tempDir.resolve("move-no-reload"));
|
||||
final CountingLoader loader = countingLoader();
|
||||
final FileSystemPackerWorkspaceService service = service(ignored -> { }, loader);
|
||||
|
||||
service.listAssets(new ListAssetsRequest(project(projectRoot)));
|
||||
assertEquals(1, loader.loadCount());
|
||||
|
||||
final MoveAssetResult moveResult = service.moveAsset(new MoveAssetRequest(
|
||||
project(projectRoot),
|
||||
AssetReference.forRelativeAssetRoot("orphans/ui_sounds"),
|
||||
"recovered/ui_sounds_moved"));
|
||||
|
||||
assertEquals(PackerOperationStatus.SUCCESS, moveResult.status());
|
||||
assertEquals(1, loader.loadCount());
|
||||
|
||||
final var details = service.getAssetDetails(new GetAssetDetailsRequest(project(projectRoot), moveResult.assetReference()));
|
||||
assertEquals(PackerOperationStatus.SUCCESS, details.status());
|
||||
assertEquals(1, loader.loadCount());
|
||||
}
|
||||
|
||||
@Test
|
||||
void deletesUnregisteredAssetJsonAndRemovesItFromSnapshot() throws Exception {
|
||||
final Path projectRoot = copyFixture("workspaces/orphan-valid", tempDir.resolve("delete-unregistered"));
|
||||
|
||||
@ -213,6 +213,8 @@ public enum I18n {
|
||||
ASSETS_RELOCATE_WIZARD_STEP_DESTINATION_DESCRIPTION("assets.relocateWizard.step.destination.description"),
|
||||
ASSETS_RELOCATE_WIZARD_STEP_SUMMARY_TITLE("assets.relocateWizard.step.summary.title"),
|
||||
ASSETS_RELOCATE_WIZARD_STEP_SUMMARY_DESCRIPTION("assets.relocateWizard.step.summary.description"),
|
||||
ASSETS_RELOCATE_WIZARD_STEP_WAITING_TITLE("assets.relocateWizard.step.waiting.title"),
|
||||
ASSETS_RELOCATE_WIZARD_STEP_WAITING_DESCRIPTION("assets.relocateWizard.step.waiting.description"),
|
||||
ASSETS_RELOCATE_WIZARD_LABEL_CURRENT_ROOT("assets.relocateWizard.label.currentRoot"),
|
||||
ASSETS_RELOCATE_WIZARD_LABEL_DESTINATION_PARENT("assets.relocateWizard.label.destinationParent"),
|
||||
ASSETS_RELOCATE_WIZARD_LABEL_DESTINATION_NAME("assets.relocateWizard.label.destinationName"),
|
||||
@ -223,6 +225,11 @@ public enum I18n {
|
||||
ASSETS_RELOCATE_WIZARD_NOTE("assets.relocateWizard.note"),
|
||||
ASSETS_RELOCATE_WIZARD_BUTTON_CONFIRM("assets.relocateWizard.button.confirm"),
|
||||
ASSETS_RELOCATE_WIZARD_BUTTON_PREVIEW("assets.relocateWizard.button.preview"),
|
||||
ASSETS_RELOCATE_WIZARD_ERROR_PARENT("assets.relocateWizard.error.parent"),
|
||||
ASSETS_RELOCATE_WIZARD_ERROR_NAME("assets.relocateWizard.error.name"),
|
||||
ASSETS_RELOCATE_WIZARD_ERROR_OUTSIDE_ASSETS("assets.relocateWizard.error.outsideAssets"),
|
||||
ASSETS_RELOCATE_WIZARD_ERROR_TARGET_ALREADY_ASSET("assets.relocateWizard.error.targetAlreadyAsset"),
|
||||
ASSETS_RELOCATE_WIZARD_ERROR_TARGET_SAME("assets.relocateWizard.error.targetSame"),
|
||||
WORKSPACE_DEBUG("workspace.debug"),
|
||||
|
||||
|
||||
|
||||
@ -119,7 +119,7 @@ public final class AssetWorkspace extends Workspace {
|
||||
}
|
||||
AddAssetWizard.showAndWait(root.getScene().getWindow(), projectReference).ifPresent(assetReference -> {
|
||||
workspaceEventBus.publish(new StudioAssetsRefreshRequestedEvent(assetReference));
|
||||
workspaceEventBus.publish(new StudioAssetsWorkspaceSelectionRequestedEvent(assetReference));
|
||||
workspaceEventBus.publish(new StudioAssetsWorkspaceSelectionRequestedEvent(assetReference, false));
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@ -8,16 +8,9 @@ import javafx.scene.control.ScrollPane;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.Priority;
|
||||
import javafx.scene.layout.VBox;
|
||||
import p.packer.messages.AssetReference;
|
||||
import p.packer.dtos.PackerAssetActionAvailabilityDTO;
|
||||
import p.packer.dtos.PackerAssetDetailsDTO;
|
||||
import p.packer.messages.DeleteAssetRequest;
|
||||
import p.packer.messages.DeleteAssetResult;
|
||||
import p.packer.messages.GetAssetDetailsRequest;
|
||||
import p.packer.messages.GetAssetActionsRequest;
|
||||
import p.packer.messages.RegisterAssetRequest;
|
||||
import p.packer.messages.RegisterAssetResult;
|
||||
import p.packer.messages.assets.AssetAction;
|
||||
import p.packer.messages.*;
|
||||
import p.studio.Container;
|
||||
import p.studio.events.StudioWorkspaceEventBus;
|
||||
import p.studio.projects.ProjectReference;
|
||||
@ -32,6 +25,7 @@ import p.studio.workspaces.assets.messages.events.StudioAssetsDetailsViewStateCh
|
||||
import p.studio.workspaces.assets.messages.events.StudioAssetsRefreshRequestedEvent;
|
||||
import p.studio.workspaces.assets.messages.events.StudioAssetsWorkspaceSelectionRequestedEvent;
|
||||
import p.studio.workspaces.assets.wizards.DeleteAssetDialog;
|
||||
import p.studio.workspaces.assets.wizards.MoveAssetWizard;
|
||||
import p.studio.workspaces.framework.StudioEventAware;
|
||||
import p.studio.workspaces.framework.StudioEventBindings;
|
||||
|
||||
@ -110,7 +104,7 @@ public final class AssetDetailsControl extends VBox implements StudioEventAware
|
||||
}
|
||||
});
|
||||
eventBindings.listen(workspaceBus, StudioAssetsWorkspaceSelectionRequestedEvent.class).handle(event -> {
|
||||
if (!isCurrentSelection(event.assetReference())) {
|
||||
if (event.forceUpdate() || !isCurrentSelection(event.assetReference())) {
|
||||
loadSelection(event.assetReference());
|
||||
}
|
||||
});
|
||||
@ -309,10 +303,25 @@ public final class AssetDetailsControl extends VBox implements StudioEventAware
|
||||
renderActions();
|
||||
Container.backgroundTasks().submit(() -> registerSelectedAsset(viewState.selectedAssetReference()));
|
||||
}
|
||||
case MOVE -> openMoveWizard();
|
||||
case DELETE -> confirmDeleteAction();
|
||||
}
|
||||
}
|
||||
|
||||
private void openMoveWizard() {
|
||||
if (viewState.selectedAssetDetails() == null || getScene() == null || viewState.selectedAssetReference() == null) {
|
||||
return;
|
||||
}
|
||||
MoveAssetWizard.showAndWait(
|
||||
getScene().getWindow(),
|
||||
projectReference,
|
||||
viewState.selectedAssetReference(),
|
||||
viewState.selectedAssetDetails().summary()).ifPresent(assetReference -> {
|
||||
workspaceBus.publish(new StudioAssetsRefreshRequestedEvent(assetReference));
|
||||
workspaceBus.publish(new StudioAssetsWorkspaceSelectionRequestedEvent(assetReference, true));
|
||||
});
|
||||
}
|
||||
|
||||
private void confirmDeleteAction() {
|
||||
if (viewState.selectedAssetDetails() == null || getScene() == null) {
|
||||
return;
|
||||
@ -346,7 +355,7 @@ public final class AssetDetailsControl extends VBox implements StudioEventAware
|
||||
if (result.status() == p.packer.messages.PackerOperationStatus.SUCCESS && result.assetReference() != null) {
|
||||
actionFeedbackMessage = null;
|
||||
workspaceBus.publish(new StudioAssetsRefreshRequestedEvent(result.assetReference()));
|
||||
workspaceBus.publish(new StudioAssetsWorkspaceSelectionRequestedEvent(result.assetReference()));
|
||||
workspaceBus.publish(new StudioAssetsWorkspaceSelectionRequestedEvent(result.assetReference(), false));
|
||||
return;
|
||||
}
|
||||
actionFeedbackMessage = Objects.requireNonNullElse(result.summary(), "Unable to register asset.");
|
||||
|
||||
@ -108,6 +108,7 @@ public final class AssetDetailsUiSupport {
|
||||
public static String actionLabel(AssetAction action) {
|
||||
return switch (action) {
|
||||
case REGISTER -> Container.i18n().text(I18n.ASSETS_ACTION_REGISTER);
|
||||
case MOVE -> Container.i18n().text(I18n.ASSETS_ACTION_RELOCATE);
|
||||
case DELETE -> Container.i18n().text(I18n.ASSETS_ACTION_DELETE);
|
||||
};
|
||||
}
|
||||
|
||||
@ -42,7 +42,7 @@ public final class AssetListItemControl extends VBox {
|
||||
getStyleClass().setAll("assets-workspace-asset-row", assetRowToneClass(summary.assetFamily()));
|
||||
getChildren().setAll(createTopLine(), createPathLabel());
|
||||
setOnMouseClicked(event -> workspaceBus.publish(
|
||||
new StudioAssetsWorkspaceSelectionRequestedEvent(summary.assetReference())));
|
||||
new StudioAssetsWorkspaceSelectionRequestedEvent(summary.assetReference(), false)));
|
||||
}
|
||||
|
||||
private HBox createTopLine() {
|
||||
|
||||
@ -6,7 +6,8 @@ import p.studio.events.StudioEvent;
|
||||
import java.util.Objects;
|
||||
|
||||
public record StudioAssetsWorkspaceSelectionRequestedEvent(
|
||||
AssetReference assetReference) implements StudioEvent {
|
||||
AssetReference assetReference,
|
||||
boolean forceUpdate) implements StudioEvent {
|
||||
public StudioAssetsWorkspaceSelectionRequestedEvent {
|
||||
Objects.requireNonNull(assetReference, "assetReference");
|
||||
}
|
||||
|
||||
@ -0,0 +1,420 @@
|
||||
package p.studio.workspaces.assets.wizards;
|
||||
|
||||
import javafx.application.Platform;
|
||||
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.ProgressIndicator;
|
||||
import javafx.scene.control.TextField;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.Priority;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.stage.DirectoryChooser;
|
||||
import javafx.stage.Modality;
|
||||
import javafx.stage.Stage;
|
||||
import javafx.stage.Window;
|
||||
import p.packer.messages.AssetReference;
|
||||
import p.packer.messages.MoveAssetRequest;
|
||||
import p.packer.messages.MoveAssetResult;
|
||||
import p.packer.messages.PackerOperationStatus;
|
||||
import p.studio.Container;
|
||||
import p.studio.projects.ProjectReference;
|
||||
import p.studio.utilities.i18n.I18n;
|
||||
import p.studio.workspaces.assets.details.AssetDetailsUiSupport;
|
||||
import p.studio.workspaces.assets.messages.AssetWorkspaceAssetSummary;
|
||||
|
||||
import java.io.File;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
public final class MoveAssetWizard {
|
||||
private final ProjectReference projectReference;
|
||||
private final AssetReference assetReference;
|
||||
private final AssetWorkspaceAssetSummary assetSummary;
|
||||
private final Stage stage;
|
||||
private final AtomicReference<AssetReference> result = new AtomicReference<>();
|
||||
private final Label stepTitle = new Label();
|
||||
private final Label stepDescription = new Label();
|
||||
private final VBox stepBody = new VBox(12);
|
||||
private final Label feedbackLabel = new Label();
|
||||
private final Button backButton = new Button();
|
||||
private final Button nextButton = new Button();
|
||||
private final Button confirmButton = new Button();
|
||||
private final TextField destinationParentField = new TextField();
|
||||
private final TextField destinationNameField = new TextField();
|
||||
private final TextField targetRootField = new TextField();
|
||||
|
||||
private int stepIndex;
|
||||
private boolean moving;
|
||||
|
||||
private MoveAssetWizard(
|
||||
Window owner,
|
||||
ProjectReference projectReference,
|
||||
AssetReference assetReference,
|
||||
AssetWorkspaceAssetSummary assetSummary) {
|
||||
this.projectReference = Objects.requireNonNull(projectReference, "projectReference");
|
||||
this.assetReference = Objects.requireNonNull(assetReference, "assetReference");
|
||||
this.assetSummary = Objects.requireNonNull(assetSummary, "assetSummary");
|
||||
this.stage = new Stage();
|
||||
stage.initOwner(owner);
|
||||
stage.initModality(Modality.WINDOW_MODAL);
|
||||
stage.setTitle(Container.i18n().text(I18n.ASSETS_RELOCATE_WIZARD_TITLE));
|
||||
stage.setScene(new Scene(buildRoot(), 760, 460));
|
||||
stage.getScene().getStylesheets().add(Container.theme().getDefaultTheme());
|
||||
stage.setOnCloseRequest(event -> {
|
||||
if (moving) {
|
||||
event.consume();
|
||||
}
|
||||
});
|
||||
|
||||
destinationParentField.setText(initialDestinationParent());
|
||||
destinationNameField.setText(assetSummary.assetRoot().getFileName().toString());
|
||||
targetRootField.setEditable(false);
|
||||
renderStep();
|
||||
}
|
||||
|
||||
public static Optional<AssetReference> showAndWait(
|
||||
Window owner,
|
||||
ProjectReference projectReference,
|
||||
AssetReference assetReference,
|
||||
AssetWorkspaceAssetSummary assetSummary) {
|
||||
final MoveAssetWizard wizard = new MoveAssetWizard(owner, projectReference, assetReference, assetSummary);
|
||||
wizard.stage.showAndWait();
|
||||
return Optional.ofNullable(wizard.result.get());
|
||||
}
|
||||
|
||||
private VBox buildRoot() {
|
||||
stepTitle.getStyleClass().add("studio-launcher-section-title");
|
||||
stepDescription.getStyleClass().add("studio-launcher-subtitle");
|
||||
feedbackLabel.getStyleClass().add("studio-launcher-feedback");
|
||||
feedbackLabel.setWrapText(true);
|
||||
|
||||
backButton.textProperty().bind(Container.i18n().bind(I18n.WIZARD_BACK));
|
||||
backButton.getStyleClass().addAll("studio-button", "studio-button-secondary");
|
||||
backButton.setOnAction(ignored -> goBack());
|
||||
|
||||
nextButton.textProperty().bind(Container.i18n().bind(I18n.WIZARD_NEXT));
|
||||
nextButton.getStyleClass().addAll("studio-button", "studio-button-primary");
|
||||
nextButton.setOnAction(ignored -> goNext());
|
||||
|
||||
confirmButton.textProperty().bind(Container.i18n().bind(I18n.ASSETS_RELOCATE_WIZARD_BUTTON_CONFIRM));
|
||||
confirmButton.getStyleClass().addAll("studio-button", "studio-button-primary");
|
||||
confirmButton.setOnAction(ignored -> confirmMove());
|
||||
|
||||
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 -> {
|
||||
if (!moving) {
|
||||
stage.close();
|
||||
}
|
||||
});
|
||||
|
||||
final HBox actions = new HBox(12, backButton, nextButton, confirmButton, cancelButton);
|
||||
actions.setAlignment(Pos.CENTER_RIGHT);
|
||||
|
||||
final VBox root = new VBox(16, stepTitle, stepDescription, stepBody, feedbackLabel, actions);
|
||||
root.setPadding(new Insets(24));
|
||||
VBox.setVgrow(stepBody, Priority.ALWAYS);
|
||||
return root;
|
||||
}
|
||||
|
||||
private void renderStep() {
|
||||
backButton.setDisable(stepIndex == 0 || moving);
|
||||
nextButton.setVisible(stepIndex == 0 && !moving);
|
||||
nextButton.setManaged(stepIndex == 0 && !moving);
|
||||
confirmButton.setVisible(stepIndex == 1 && !moving);
|
||||
confirmButton.setManaged(stepIndex == 1 && !moving);
|
||||
if (!moving && stepIndex != 2 && feedbackLabel.getText() == null) {
|
||||
feedbackLabel.setText("");
|
||||
}
|
||||
|
||||
switch (stepIndex) {
|
||||
case 0 -> renderDestinationStep();
|
||||
case 1 -> renderSummaryStep();
|
||||
case 2 -> renderWaitingStep();
|
||||
default -> throw new IllegalStateException("Unknown wizard step: " + stepIndex);
|
||||
}
|
||||
}
|
||||
|
||||
private void renderDestinationStep() {
|
||||
stepTitle.textProperty().unbind();
|
||||
stepDescription.textProperty().unbind();
|
||||
stepTitle.textProperty().bind(Container.i18n().bind(I18n.ASSETS_RELOCATE_WIZARD_STEP_DESTINATION_TITLE));
|
||||
stepDescription.textProperty().bind(Container.i18n().bind(I18n.ASSETS_RELOCATE_WIZARD_STEP_DESTINATION_DESCRIPTION));
|
||||
|
||||
targetRootField.setText(displayTargetRoot());
|
||||
|
||||
final Button browseButton = new Button();
|
||||
browseButton.textProperty().bind(Container.i18n().bind(I18n.WIZARD_BROWSE));
|
||||
browseButton.getStyleClass().addAll("studio-button", "studio-button-secondary");
|
||||
browseButton.setOnAction(ignored -> browseForDestinationParent());
|
||||
browseButton.setDisable(moving);
|
||||
|
||||
destinationParentField.setDisable(moving);
|
||||
destinationNameField.setDisable(moving);
|
||||
|
||||
final HBox destinationParentRow = new HBox(12, destinationParentField, browseButton);
|
||||
HBox.setHgrow(destinationParentField, Priority.ALWAYS);
|
||||
|
||||
final Label note = new Label(Container.i18n().format(
|
||||
I18n.ASSETS_RELOCATE_WIZARD_ASSETS_ROOT_HINT,
|
||||
assetsRoot().toString()));
|
||||
note.getStyleClass().add("studio-launcher-feedback");
|
||||
note.setWrapText(true);
|
||||
|
||||
stepBody.getChildren().setAll(
|
||||
AssetDetailsUiSupport.createKeyValueRow(
|
||||
Container.i18n().text(I18n.ASSETS_RELOCATE_WIZARD_LABEL_CURRENT_ROOT),
|
||||
currentRelativeRoot()),
|
||||
AssetDetailsUiSupport.createKeyValueRow(
|
||||
Container.i18n().text(I18n.ASSETS_RELOCATE_WIZARD_LABEL_DESTINATION_PARENT),
|
||||
destinationParentRow),
|
||||
AssetDetailsUiSupport.createKeyValueRow(
|
||||
Container.i18n().text(I18n.ASSETS_RELOCATE_WIZARD_LABEL_DESTINATION_NAME),
|
||||
destinationNameField),
|
||||
AssetDetailsUiSupport.createKeyValueRow(
|
||||
Container.i18n().text(I18n.ASSETS_RELOCATE_WIZARD_LABEL_TARGET_ROOT),
|
||||
targetRootField),
|
||||
note);
|
||||
}
|
||||
|
||||
private void renderSummaryStep() {
|
||||
stepTitle.textProperty().unbind();
|
||||
stepDescription.textProperty().unbind();
|
||||
stepTitle.textProperty().bind(Container.i18n().bind(I18n.ASSETS_RELOCATE_WIZARD_STEP_SUMMARY_TITLE));
|
||||
stepDescription.textProperty().bind(Container.i18n().bind(I18n.ASSETS_RELOCATE_WIZARD_STEP_SUMMARY_DESCRIPTION));
|
||||
stepBody.getChildren().setAll(
|
||||
AssetDetailsUiSupport.createKeyValueRow(
|
||||
Container.i18n().text(I18n.ASSETS_RELOCATE_WIZARD_LABEL_CURRENT_ROOT),
|
||||
currentRelativeRoot()),
|
||||
AssetDetailsUiSupport.createKeyValueRow(
|
||||
Container.i18n().text(I18n.ASSETS_RELOCATE_WIZARD_LABEL_DESTINATION_PARENT),
|
||||
displayDestinationParent()),
|
||||
AssetDetailsUiSupport.createKeyValueRow(
|
||||
Container.i18n().text(I18n.ASSETS_RELOCATE_WIZARD_LABEL_DESTINATION_NAME),
|
||||
normalizedDestinationName()),
|
||||
AssetDetailsUiSupport.createKeyValueRow(
|
||||
Container.i18n().text(I18n.ASSETS_RELOCATE_WIZARD_LABEL_TARGET_ROOT),
|
||||
targetRelativeRoot()),
|
||||
AssetDetailsUiSupport.createSectionMessage(Container.i18n().text(I18n.ASSETS_RELOCATE_WIZARD_NOTE)));
|
||||
}
|
||||
|
||||
private void renderWaitingStep() {
|
||||
stepTitle.textProperty().unbind();
|
||||
stepDescription.textProperty().unbind();
|
||||
stepTitle.textProperty().bind(Container.i18n().bind(I18n.ASSETS_RELOCATE_WIZARD_STEP_WAITING_TITLE));
|
||||
stepDescription.textProperty().bind(Container.i18n().bind(I18n.ASSETS_RELOCATE_WIZARD_STEP_WAITING_DESCRIPTION));
|
||||
final ProgressIndicator indicator = new ProgressIndicator();
|
||||
final VBox box = new VBox(16, indicator, AssetDetailsUiSupport.createSectionMessage(targetRelativeRoot()));
|
||||
box.setAlignment(Pos.CENTER_LEFT);
|
||||
stepBody.getChildren().setAll(box);
|
||||
}
|
||||
|
||||
private void goBack() {
|
||||
if (moving || stepIndex == 0) {
|
||||
return;
|
||||
}
|
||||
stepIndex -= 1;
|
||||
renderStep();
|
||||
}
|
||||
|
||||
private void goNext() {
|
||||
if (moving || !validateDestination(true)) {
|
||||
return;
|
||||
}
|
||||
feedbackLabel.setText("");
|
||||
stepIndex = 1;
|
||||
renderStep();
|
||||
}
|
||||
|
||||
private void confirmMove() {
|
||||
if (moving || !validateDestination(true)) {
|
||||
return;
|
||||
}
|
||||
moving = true;
|
||||
feedbackLabel.setText("");
|
||||
stepIndex = 2;
|
||||
renderStep();
|
||||
final MoveAssetRequest request = new MoveAssetRequest(
|
||||
projectReference.toPackerProjectContext(),
|
||||
assetReference,
|
||||
targetRelativeRoot());
|
||||
Container.backgroundTasks().submit(() -> moveAsset(request));
|
||||
}
|
||||
|
||||
private void moveAsset(MoveAssetRequest request) {
|
||||
try {
|
||||
final MoveAssetResult moveResult = Container.packer().workspaceService().moveAsset(request);
|
||||
Platform.runLater(() -> applyMoveResult(moveResult));
|
||||
} catch (RuntimeException exception) {
|
||||
Platform.runLater(() -> applyMoveFailure(exception));
|
||||
}
|
||||
}
|
||||
|
||||
private void applyMoveResult(MoveAssetResult moveResult) {
|
||||
moving = false;
|
||||
if (moveResult.status() == PackerOperationStatus.SUCCESS && moveResult.assetReference() != null) {
|
||||
result.set(moveResult.assetReference());
|
||||
stage.close();
|
||||
return;
|
||||
}
|
||||
feedbackLabel.setText(Objects.requireNonNullElse(moveResult.summary(), "Unable to move asset."));
|
||||
stepIndex = 0;
|
||||
renderStep();
|
||||
}
|
||||
|
||||
private void applyMoveFailure(RuntimeException exception) {
|
||||
moving = false;
|
||||
feedbackLabel.setText(exception.getMessage() == null || exception.getMessage().isBlank()
|
||||
? "Unable to move asset."
|
||||
: exception.getMessage());
|
||||
stepIndex = 0;
|
||||
renderStep();
|
||||
}
|
||||
|
||||
private void browseForDestinationParent() {
|
||||
final DirectoryChooser chooser = new DirectoryChooser();
|
||||
chooser.setTitle(Container.i18n().text(I18n.ASSETS_RELOCATE_WIZARD_BROWSE_TITLE));
|
||||
chooser.setInitialDirectory(existingInitialBrowseDirectory().toFile());
|
||||
final File selected = chooser.showDialog(stage);
|
||||
if (selected == null) {
|
||||
return;
|
||||
}
|
||||
final Path selectedPath = selected.toPath().toAbsolutePath().normalize();
|
||||
if (!selectedPath.startsWith(assetsRoot())) {
|
||||
feedbackLabel.setText(Container.i18n().text(I18n.ASSETS_RELOCATE_WIZARD_ERROR_OUTSIDE_ASSETS));
|
||||
return;
|
||||
}
|
||||
destinationParentField.setText(relativeDestinationParent(selectedPath));
|
||||
targetRootField.setText(displayTargetRoot());
|
||||
}
|
||||
|
||||
private boolean validateDestination(boolean showFeedback) {
|
||||
final String parent = normalizedDestinationParent();
|
||||
if (parent == null) {
|
||||
if (showFeedback) {
|
||||
feedbackLabel.setText(Container.i18n().text(I18n.ASSETS_RELOCATE_WIZARD_ERROR_PARENT));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
final String name = normalizedDestinationName();
|
||||
if (name == null) {
|
||||
if (showFeedback) {
|
||||
feedbackLabel.setText(Container.i18n().text(I18n.ASSETS_RELOCATE_WIZARD_ERROR_NAME));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
final String targetRoot = targetRelativeRoot();
|
||||
if (targetRoot == null) {
|
||||
if (showFeedback) {
|
||||
feedbackLabel.setText(Container.i18n().text(I18n.ASSETS_RELOCATE_WIZARD_ERROR_OUTSIDE_ASSETS));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if (targetRoot.equals(currentRelativeRoot())) {
|
||||
if (showFeedback) {
|
||||
feedbackLabel.setText(Container.i18n().text(I18n.ASSETS_RELOCATE_WIZARD_ERROR_TARGET_SAME));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if (targetManifestPath().toFile().isFile()) {
|
||||
if (showFeedback) {
|
||||
feedbackLabel.setText(Container.i18n().text(I18n.ASSETS_RELOCATE_WIZARD_ERROR_TARGET_ALREADY_ASSET));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if (showFeedback) {
|
||||
feedbackLabel.setText("");
|
||||
}
|
||||
targetRootField.setText(displayTargetRoot());
|
||||
return true;
|
||||
}
|
||||
|
||||
private String initialDestinationParent() {
|
||||
return relativeDestinationParent(assetSummary.assetRoot().getParent());
|
||||
}
|
||||
|
||||
private String relativeDestinationParent(Path parentPath) {
|
||||
final Path normalized = Objects.requireNonNull(parentPath, "parentPath").toAbsolutePath().normalize();
|
||||
if (normalized.equals(assetsRoot())) {
|
||||
return "";
|
||||
}
|
||||
return assetsRoot().relativize(normalized).toString().replace('\\', '/');
|
||||
}
|
||||
|
||||
private Path existingInitialBrowseDirectory() {
|
||||
final String normalizedParent = normalizedDestinationParent();
|
||||
if (normalizedParent == null || normalizedParent.isBlank()) {
|
||||
return assetsRoot();
|
||||
}
|
||||
final Path candidate = assetsRoot().resolve(normalizedParent).toAbsolutePath().normalize();
|
||||
return candidate.startsWith(assetsRoot()) && candidate.toFile().isDirectory()
|
||||
? candidate
|
||||
: assetsRoot();
|
||||
}
|
||||
|
||||
private String normalizedDestinationParent() {
|
||||
final String raw = Objects.requireNonNullElse(destinationParentField.getText(), "").trim().replace('\\', '/');
|
||||
if (raw.isBlank()) {
|
||||
return "";
|
||||
}
|
||||
final Path normalized = Path.of(raw).normalize();
|
||||
if (normalized.isAbsolute() || normalized.startsWith("..")) {
|
||||
return null;
|
||||
}
|
||||
return normalized.toString().replace('\\', '/');
|
||||
}
|
||||
|
||||
private String normalizedDestinationName() {
|
||||
final String raw = Objects.requireNonNullElse(destinationNameField.getText(), "").trim();
|
||||
if (raw.isBlank()) {
|
||||
return null;
|
||||
}
|
||||
final Path normalized = Path.of(raw).normalize();
|
||||
if (normalized.isAbsolute() || normalized.startsWith("..") || normalized.getNameCount() != 1) {
|
||||
return null;
|
||||
}
|
||||
final String value = normalized.toString().replace('\\', '/');
|
||||
if (value.isBlank() || value.contains("/")) {
|
||||
return null;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
private String targetRelativeRoot() {
|
||||
final String parent = normalizedDestinationParent();
|
||||
final String name = normalizedDestinationName();
|
||||
if (parent == null || name == null) {
|
||||
return null;
|
||||
}
|
||||
return parent.isBlank() ? name : parent + "/" + name;
|
||||
}
|
||||
|
||||
private String displayDestinationParent() {
|
||||
final String parent = normalizedDestinationParent();
|
||||
return parent == null || parent.isBlank() ? "." : parent;
|
||||
}
|
||||
|
||||
private String displayTargetRoot() {
|
||||
final String targetRoot = targetRelativeRoot();
|
||||
return targetRoot == null ? "" : targetRoot;
|
||||
}
|
||||
|
||||
private Path targetManifestPath() {
|
||||
final String targetRoot = Objects.requireNonNullElse(targetRelativeRoot(), "");
|
||||
return assetsRoot().resolve(targetRoot).resolve("asset.json").toAbsolutePath().normalize();
|
||||
}
|
||||
|
||||
private String currentRelativeRoot() {
|
||||
return assetsRoot().relativize(assetSummary.assetRoot()).toString().replace('\\', '/');
|
||||
}
|
||||
|
||||
private Path assetsRoot() {
|
||||
return projectReference.rootPath().resolve("assets").toAbsolutePath().normalize();
|
||||
}
|
||||
}
|
||||
@ -100,7 +100,7 @@ assets.deleteDialog.note=Only asset.json is deleted. The asset directory and its
|
||||
assets.deleteDialog.confirm=Delete
|
||||
assets.action.includeInBuild=Include In Build
|
||||
assets.action.excludeFromBuild=Exclude From Build
|
||||
assets.action.relocate=Relocate
|
||||
assets.action.relocate=Move
|
||||
assets.action.remove=Remove
|
||||
assets.mutation.previewTitle=Preview: {0}
|
||||
assets.mutation.section.changes=Changes
|
||||
@ -204,6 +204,8 @@ assets.relocateWizard.step.destination.title=Choose Destination
|
||||
assets.relocateWizard.step.destination.description=Pick the parent directory and the new folder name for this asset.
|
||||
assets.relocateWizard.step.summary.title=Review Relocation
|
||||
assets.relocateWizard.step.summary.description=Review the mutation impact before confirming this relocation.
|
||||
assets.relocateWizard.step.waiting.title=Moving Asset
|
||||
assets.relocateWizard.step.waiting.description=Wait while the packer applies this move.
|
||||
assets.relocateWizard.label.currentRoot=Current Asset Root
|
||||
assets.relocateWizard.label.destinationParent=Destination Parent
|
||||
assets.relocateWizard.label.destinationName=Destination Folder Name
|
||||
@ -214,4 +216,9 @@ assets.relocateWizard.browse.title=Choose Destination Parent Directory
|
||||
assets.relocateWizard.note=OK applies this relocation immediately. Use Back if you need to change the destination.
|
||||
assets.relocateWizard.button.confirm=OK
|
||||
assets.relocateWizard.button.preview=Preview Relocation
|
||||
assets.relocateWizard.error.parent=Destination parent must stay inside assets/.
|
||||
assets.relocateWizard.error.name=Destination folder name is required.
|
||||
assets.relocateWizard.error.outsideAssets=The planned target root must stay inside assets/.
|
||||
assets.relocateWizard.error.targetAlreadyAsset=The planned target root already contains asset.json.
|
||||
assets.relocateWizard.error.targetSame=The planned target root must differ from the current asset root.
|
||||
workspace.debug=Debug
|
||||
|
||||
@ -3,6 +3,591 @@
|
||||
"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",
|
||||
"severity" : "SUCCESS",
|
||||
"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: ui_atlas",
|
||||
"severity" : "INFO",
|
||||
"sticky" : false
|
||||
}, {
|
||||
"source" : "Assets",
|
||||
"message" : "Discovered asset: Bigode",
|
||||
"severity" : "INFO",
|
||||
"sticky" : false
|
||||
}, {
|
||||
"source" : "Assets",
|
||||
"message" : "Discovered asset: bbb2",
|
||||
"severity" : "INFO",
|
||||
"sticky" : false
|
||||
}, {
|
||||
"source" : "Assets",
|
||||
"message" : "Asset scan started",
|
||||
"severity" : "INFO",
|
||||
"sticky" : false
|
||||
}, {
|
||||
"source" : "Assets",
|
||||
"message" : "Asset created: bbb2",
|
||||
"severity" : "SUCCESS",
|
||||
"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: 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: 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: recovered/sound -> ui/sound",
|
||||
"severity" : "SUCCESS",
|
||||
"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
|
||||
}, {
|
||||
"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
|
||||
}, {
|
||||
"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: ui/sound -> recovered/sound",
|
||||
"severity" : "SUCCESS",
|
||||
"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: 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: 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: 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: 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: sound -> ui/sound",
|
||||
"severity" : "SUCCESS",
|
||||
"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
|
||||
}, {
|
||||
"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
|
||||
}, {
|
||||
"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: ui/bigode -> bigode",
|
||||
"severity" : "SUCCESS",
|
||||
"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: Bigode",
|
||||
"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
|
||||
}, {
|
||||
"source" : "Assets",
|
||||
"message" : "Discovered asset: ui_atlas",
|
||||
"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: Bigode",
|
||||
"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
|
||||
}, {
|
||||
"source" : "Assets",
|
||||
"message" : "Discovered asset: ui_atlas",
|
||||
"severity" : "INFO",
|
||||
"sticky" : false
|
||||
}, {
|
||||
"source" : "Assets",
|
||||
"message" : "Asset scan started",
|
||||
"severity" : "INFO",
|
||||
"sticky" : false
|
||||
}, {
|
||||
"source" : "Assets",
|
||||
"message" : "Asset moved: bigode -> ui/bigode",
|
||||
"severity" : "SUCCESS",
|
||||
"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
|
||||
}, {
|
||||
"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" : "8 assets loaded",
|
||||
"severity" : "SUCCESS",
|
||||
"sticky" : false
|
||||
}, {
|
||||
"source" : "Assets",
|
||||
"message" : "Asset scan diagnostics updated.",
|
||||
@ -1913,354 +2498,4 @@
|
||||
"message" : "Discovered asset: Novo Asset",
|
||||
"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" : "8 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
|
||||
}, {
|
||||
"source" : "Assets",
|
||||
"message" : "Discovered asset: Novo Asset",
|
||||
"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" : "6 assets loaded",
|
||||
"severity" : "SUCCESS",
|
||||
"sticky" : false
|
||||
}, {
|
||||
"source" : "Assets",
|
||||
"message" : "Asset scan started",
|
||||
"severity" : "INFO",
|
||||
"sticky" : false
|
||||
}, {
|
||||
"source" : "Assets",
|
||||
"message" : "6 assets loaded",
|
||||
"severity" : "SUCCESS",
|
||||
"sticky" : false
|
||||
}, {
|
||||
"source" : "Assets",
|
||||
"message" : "Asset scan started",
|
||||
"severity" : "INFO",
|
||||
"sticky" : false
|
||||
}, {
|
||||
"source" : "Assets",
|
||||
"message" : "6 assets loaded",
|
||||
"severity" : "SUCCESS",
|
||||
"sticky" : false
|
||||
}, {
|
||||
"source" : "Assets",
|
||||
"message" : "Asset scan started",
|
||||
"severity" : "INFO",
|
||||
"sticky" : false
|
||||
}, {
|
||||
"source" : "Assets",
|
||||
"message" : "6 assets loaded",
|
||||
"severity" : "SUCCESS",
|
||||
"sticky" : false
|
||||
}, {
|
||||
"source" : "Assets",
|
||||
"message" : "Asset scan started",
|
||||
"severity" : "INFO",
|
||||
"sticky" : false
|
||||
}, {
|
||||
"source" : "Assets",
|
||||
"message" : "6 assets loaded",
|
||||
"severity" : "SUCCESS",
|
||||
"sticky" : false
|
||||
}, {
|
||||
"source" : "Assets",
|
||||
"message" : "Asset scan started",
|
||||
"severity" : "INFO",
|
||||
"sticky" : false
|
||||
}, {
|
||||
"source" : "Assets",
|
||||
"message" : "6 assets loaded",
|
||||
"severity" : "SUCCESS",
|
||||
"sticky" : false
|
||||
}, {
|
||||
"source" : "Assets",
|
||||
"message" : "Asset scan started",
|
||||
"severity" : "INFO",
|
||||
"sticky" : false
|
||||
}, {
|
||||
"source" : "Assets",
|
||||
"message" : "6 assets loaded",
|
||||
"severity" : "SUCCESS",
|
||||
"sticky" : false
|
||||
}, {
|
||||
"source" : "Assets",
|
||||
"message" : "Asset scan started",
|
||||
"severity" : "INFO",
|
||||
"sticky" : false
|
||||
}, {
|
||||
"source" : "Assets",
|
||||
"message" : "6 assets loaded",
|
||||
"severity" : "SUCCESS",
|
||||
"sticky" : false
|
||||
}, {
|
||||
"source" : "Assets",
|
||||
"message" : "Asset scan started",
|
||||
"severity" : "INFO",
|
||||
"sticky" : false
|
||||
}, {
|
||||
"source" : "Assets",
|
||||
"message" : "6 assets loaded",
|
||||
"severity" : "SUCCESS",
|
||||
"sticky" : false
|
||||
}, {
|
||||
"source" : "Assets",
|
||||
"message" : "Asset scan started",
|
||||
"severity" : "INFO",
|
||||
"sticky" : false
|
||||
}, {
|
||||
"source" : "Assets",
|
||||
"message" : "6 assets loaded",
|
||||
"severity" : "SUCCESS",
|
||||
"sticky" : false
|
||||
}, {
|
||||
"source" : "Assets",
|
||||
"message" : "Asset scan started",
|
||||
"severity" : "INFO",
|
||||
"sticky" : false
|
||||
}, {
|
||||
"source" : "Assets",
|
||||
"message" : "6 assets loaded",
|
||||
"severity" : "SUCCESS",
|
||||
"sticky" : false
|
||||
}, {
|
||||
"source" : "Assets",
|
||||
"message" : "Asset scan started",
|
||||
"severity" : "INFO",
|
||||
"sticky" : false
|
||||
}, {
|
||||
"source" : "Assets",
|
||||
"message" : "6 assets loaded",
|
||||
"severity" : "SUCCESS",
|
||||
"sticky" : false
|
||||
}, {
|
||||
"source" : "Assets",
|
||||
"message" : "Asset scan started",
|
||||
"severity" : "INFO",
|
||||
"sticky" : false
|
||||
}, {
|
||||
"source" : "Assets",
|
||||
"message" : "5 assets loaded",
|
||||
"severity" : "SUCCESS",
|
||||
"sticky" : false
|
||||
}, {
|
||||
"source" : "Assets",
|
||||
"message" : "Asset scan started",
|
||||
"severity" : "INFO",
|
||||
"sticky" : false
|
||||
}, {
|
||||
"source" : "Assets",
|
||||
"message" : "5 assets loaded",
|
||||
"severity" : "SUCCESS",
|
||||
"sticky" : false
|
||||
}, {
|
||||
"source" : "Assets",
|
||||
"message" : "Asset scan started",
|
||||
"severity" : "INFO",
|
||||
"sticky" : false
|
||||
}, {
|
||||
"source" : "Assets",
|
||||
"message" : "5 assets loaded",
|
||||
"severity" : "SUCCESS",
|
||||
"sticky" : false
|
||||
}, {
|
||||
"source" : "Assets",
|
||||
"message" : "Asset scan started",
|
||||
"severity" : "INFO",
|
||||
"sticky" : false
|
||||
}, {
|
||||
"source" : "Assets",
|
||||
"message" : "4 assets loaded",
|
||||
"severity" : "SUCCESS",
|
||||
"sticky" : false
|
||||
}, {
|
||||
"source" : "Assets",
|
||||
"message" : "Asset scan started",
|
||||
"severity" : "INFO",
|
||||
"sticky" : false
|
||||
}, {
|
||||
"source" : "Assets",
|
||||
"message" : "4 assets loaded",
|
||||
"severity" : "SUCCESS",
|
||||
"sticky" : false
|
||||
}, {
|
||||
"source" : "Assets",
|
||||
"message" : "Asset scan started",
|
||||
"severity" : "INFO",
|
||||
"sticky" : false
|
||||
}, {
|
||||
"source" : "Assets",
|
||||
"message" : "4 assets loaded",
|
||||
"severity" : "SUCCESS",
|
||||
"sticky" : false
|
||||
}, {
|
||||
"source" : "Assets",
|
||||
"message" : "Asset scan started",
|
||||
"severity" : "INFO",
|
||||
"sticky" : false
|
||||
}, {
|
||||
"source" : "Assets",
|
||||
"message" : "4 assets loaded",
|
||||
"severity" : "SUCCESS",
|
||||
"sticky" : false
|
||||
}, {
|
||||
"source" : "Assets",
|
||||
"message" : "Asset scan started",
|
||||
"severity" : "INFO",
|
||||
"sticky" : false
|
||||
}, {
|
||||
"source" : "Assets",
|
||||
"message" : "4 assets loaded",
|
||||
"severity" : "SUCCESS",
|
||||
"sticky" : false
|
||||
}, {
|
||||
"source" : "Assets",
|
||||
"message" : "Asset scan started",
|
||||
"severity" : "INFO",
|
||||
"sticky" : false
|
||||
}, {
|
||||
"source" : "Assets",
|
||||
"message" : "4 assets loaded",
|
||||
"severity" : "SUCCESS",
|
||||
"sticky" : false
|
||||
}, {
|
||||
"source" : "Assets",
|
||||
"message" : "Asset scan started",
|
||||
"severity" : "INFO",
|
||||
"sticky" : false
|
||||
}, {
|
||||
"source" : "Assets",
|
||||
"message" : "4 assets loaded",
|
||||
"severity" : "SUCCESS",
|
||||
"sticky" : false
|
||||
}, {
|
||||
"source" : "Assets",
|
||||
"message" : "Asset scan started",
|
||||
"severity" : "INFO",
|
||||
"sticky" : false
|
||||
}, {
|
||||
"source" : "Assets",
|
||||
"message" : "4 assets loaded",
|
||||
"severity" : "SUCCESS",
|
||||
"sticky" : false
|
||||
}, {
|
||||
"source" : "Assets",
|
||||
"message" : "Asset scan started",
|
||||
"severity" : "INFO",
|
||||
"sticky" : false
|
||||
}, {
|
||||
"source" : "Studio",
|
||||
"message" : "Project opened: main",
|
||||
"severity" : "SUCCESS",
|
||||
"sticky" : false
|
||||
}, {
|
||||
"source" : "Assets",
|
||||
"message" : "4 assets loaded",
|
||||
"severity" : "SUCCESS",
|
||||
"sticky" : false
|
||||
}, {
|
||||
"source" : "Assets",
|
||||
"message" : "Asset scan started",
|
||||
"severity" : "INFO",
|
||||
"sticky" : false
|
||||
}, {
|
||||
"source" : "Assets",
|
||||
"message" : "4 assets loaded",
|
||||
"severity" : "SUCCESS",
|
||||
"sticky" : false
|
||||
}, {
|
||||
"source" : "Assets",
|
||||
"message" : "Asset scan started",
|
||||
"severity" : "INFO",
|
||||
"sticky" : false
|
||||
}, {
|
||||
"source" : "Assets",
|
||||
"message" : "4 assets loaded",
|
||||
"severity" : "SUCCESS",
|
||||
"sticky" : false
|
||||
}, {
|
||||
"source" : "Assets",
|
||||
"message" : "Asset scan started",
|
||||
"severity" : "INFO",
|
||||
"sticky" : false
|
||||
}, {
|
||||
"source" : "Assets",
|
||||
"message" : "4 assets loaded",
|
||||
"severity" : "SUCCESS",
|
||||
"sticky" : false
|
||||
}, {
|
||||
"source" : "Assets",
|
||||
"message" : "Asset scan started",
|
||||
"severity" : "INFO",
|
||||
"sticky" : false
|
||||
}, {
|
||||
"source" : "Assets",
|
||||
"message" : "4 assets loaded",
|
||||
"severity" : "SUCCESS",
|
||||
"sticky" : false
|
||||
}, {
|
||||
"source" : "Assets",
|
||||
"message" : "Asset scan started",
|
||||
"severity" : "INFO",
|
||||
"sticky" : false
|
||||
} ]
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"schema_version" : 1,
|
||||
"next_asset_id" : 14,
|
||||
"next_asset_id" : 15,
|
||||
"assets" : [ {
|
||||
"asset_id" : 3,
|
||||
"asset_uuid" : "21953cb8-4101-4790-9e5e-d95f5fbc9b5a",
|
||||
@ -14,7 +14,7 @@
|
||||
}, {
|
||||
"asset_id" : 8,
|
||||
"asset_uuid" : "9a7386e7-6f0e-4e4c-9919-0de71e0b7031",
|
||||
"root" : "sound",
|
||||
"root" : "ui/sound",
|
||||
"included_in_build" : true
|
||||
}, {
|
||||
"asset_id" : 9,
|
||||
@ -36,5 +36,10 @@
|
||||
"asset_uuid" : "4d9847b0-5a23-421f-8b78-bf3909ca2281",
|
||||
"root" : "recovered/one-more-atlas",
|
||||
"included_in_build" : true
|
||||
}, {
|
||||
"asset_id" : 14,
|
||||
"asset_uuid" : "f64d3bfe-443d-4703-b62a-face19a32cac",
|
||||
"root" : "recovered/bbb2",
|
||||
"included_in_build" : true
|
||||
} ]
|
||||
}
|
||||
14
test-projects/main/assets/recovered/bbb2/asset.json
Normal file
14
test-projects/main/assets/recovered/bbb2/asset.json
Normal file
@ -0,0 +1,14 @@
|
||||
{
|
||||
"schema_version" : 1,
|
||||
"asset_uuid" : "f64d3bfe-443d-4703-b62a-face19a32cac",
|
||||
"name" : "bbb2",
|
||||
"type" : "image_bank",
|
||||
"inputs" : { },
|
||||
"output" : {
|
||||
"codec" : "NONE",
|
||||
"format" : "TILES/indexed_v1"
|
||||
},
|
||||
"preload" : {
|
||||
"enabled" : false
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user