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)
|
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)
|
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:
|
Current wave discipline from `PR-11` onward:
|
||||||
|
|
||||||
@ -92,4 +93,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-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);
|
RegisterAssetResult registerAsset(RegisterAssetRequest request);
|
||||||
|
|
||||||
|
MoveAssetResult moveAsset(MoveAssetRequest request);
|
||||||
|
|
||||||
DeleteAssetResult deleteAsset(DeleteAssetRequest request);
|
DeleteAssetResult deleteAsset(DeleteAssetRequest request);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -49,4 +49,15 @@ public record AssetReference(String value) {
|
|||||||
}
|
}
|
||||||
return 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 {
|
public enum AssetAction {
|
||||||
REGISTER,
|
REGISTER,
|
||||||
|
MOVE,
|
||||||
DELETE
|
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.AssetReference;
|
||||||
import p.packer.messages.GetAssetActionsRequest;
|
import p.packer.messages.GetAssetActionsRequest;
|
||||||
import p.packer.messages.GetAssetActionsResult;
|
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.OutputFormatCatalog;
|
||||||
import p.packer.messages.assets.PackerAssetState;
|
import p.packer.messages.assets.PackerAssetState;
|
||||||
import p.packer.messages.assets.PackerBuildParticipation;
|
import p.packer.messages.assets.PackerBuildParticipation;
|
||||||
@ -31,6 +33,7 @@ import p.packer.PackerWorkspaceService;
|
|||||||
import p.packer.models.PackerAssetDeclarationParseResult;
|
import p.packer.models.PackerAssetDeclarationParseResult;
|
||||||
import p.packer.models.PackerDeleteAssetEvaluation;
|
import p.packer.models.PackerDeleteAssetEvaluation;
|
||||||
import p.packer.models.PackerAssetIdentity;
|
import p.packer.models.PackerAssetIdentity;
|
||||||
|
import p.packer.models.PackerMoveAssetEvaluation;
|
||||||
import p.packer.models.PackerRegisterAssetEvaluation;
|
import p.packer.models.PackerRegisterAssetEvaluation;
|
||||||
import p.packer.models.PackerAssetSummary;
|
import p.packer.models.PackerAssetSummary;
|
||||||
import p.packer.models.PackerDiagnostic;
|
import p.packer.models.PackerDiagnostic;
|
||||||
@ -171,6 +174,14 @@ public final class FileSystemPackerWorkspaceService implements PackerWorkspaceSe
|
|||||||
return writeCoordinator.execute(project, () -> registerAssetInWriteLane(safeRequest, events));
|
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
|
@Override
|
||||||
public DeleteAssetResult deleteAsset(DeleteAssetRequest request) {
|
public DeleteAssetResult deleteAsset(DeleteAssetRequest request) {
|
||||||
final DeleteAssetRequest safeRequest = Objects.requireNonNull(request, "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(
|
private CreateAssetResult failureResult(
|
||||||
PackerOperationEventEmitter events,
|
PackerOperationEventEmitter events,
|
||||||
String summary,
|
String summary,
|
||||||
@ -399,6 +473,22 @@ public final class FileSystemPackerWorkspaceService implements PackerWorkspaceSe
|
|||||||
return result;
|
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 {
|
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);
|
||||||
@ -504,4 +594,13 @@ public final class FileSystemPackerWorkspaceService implements PackerWorkspaceSe
|
|||||||
.toList();
|
.toList();
|
||||||
return registry.withAssets(updatedEntries, registry.nextAssetId());
|
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;
|
package p.packer.services;
|
||||||
|
|
||||||
import p.packer.dtos.PackerAssetActionAvailabilityDTO;
|
|
||||||
import p.packer.messages.AssetReference;
|
import p.packer.messages.AssetReference;
|
||||||
import p.packer.messages.GetAssetActionsRequest;
|
import p.packer.messages.GetAssetActionsRequest;
|
||||||
import p.packer.messages.GetAssetActionsResult;
|
import p.packer.messages.GetAssetActionsResult;
|
||||||
@ -14,6 +13,7 @@ import p.packer.models.PackerAssetDeclaration;
|
|||||||
import p.packer.models.PackerAssetDeclarationParseResult;
|
import p.packer.models.PackerAssetDeclarationParseResult;
|
||||||
import p.packer.models.PackerDeleteAssetEvaluation;
|
import p.packer.models.PackerDeleteAssetEvaluation;
|
||||||
import p.packer.models.PackerDiagnostic;
|
import p.packer.models.PackerDiagnostic;
|
||||||
|
import p.packer.models.PackerMoveAssetEvaluation;
|
||||||
import p.packer.models.PackerRegisterAssetEvaluation;
|
import p.packer.models.PackerRegisterAssetEvaluation;
|
||||||
import p.packer.models.PackerRegistryEntry;
|
import p.packer.models.PackerRegistryEntry;
|
||||||
import p.packer.models.PackerResolvedAssetReference;
|
import p.packer.models.PackerResolvedAssetReference;
|
||||||
@ -44,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 PackerMoveAssetEvaluation moveEvaluation = evaluateMove(snapshot, project, request.assetReference(), null);
|
||||||
final PackerDeleteAssetEvaluation deleteEvaluation = evaluateDelete(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()) {
|
||||||
@ -53,6 +54,13 @@ public final class PackerAssetActionReadService {
|
|||||||
true,
|
true,
|
||||||
registerEvaluation.reason()));
|
registerEvaluation.reason()));
|
||||||
}
|
}
|
||||||
|
if (moveEvaluation.resolved().runtimeAsset().isPresent()) {
|
||||||
|
actions.add(new PackerAssetActionAvailability(
|
||||||
|
AssetAction.MOVE,
|
||||||
|
moveEvaluation.canMove(),
|
||||||
|
true,
|
||||||
|
moveEvaluation.reason()));
|
||||||
|
}
|
||||||
if (deleteEvaluation.resolved().runtimeAsset().isPresent()) {
|
if (deleteEvaluation.resolved().runtimeAsset().isPresent()) {
|
||||||
actions.add(new PackerAssetActionAvailability(
|
actions.add(new PackerAssetActionAvailability(
|
||||||
AssetAction.DELETE,
|
AssetAction.DELETE,
|
||||||
@ -62,9 +70,12 @@ public final class PackerAssetActionReadService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final PackerOperationStatus status;
|
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;
|
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;
|
status = PackerOperationStatus.PARTIAL;
|
||||||
} else {
|
} else {
|
||||||
status = PackerOperationStatus.SUCCESS;
|
status = PackerOperationStatus.SUCCESS;
|
||||||
@ -73,7 +84,10 @@ 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(combinedDiagnostics(registerEvaluation.diagnostics(), deleteEvaluation.diagnostics())));
|
PackerReadMessageMapper.toDiagnosticDTOs(combinedDiagnostics(
|
||||||
|
registerEvaluation.diagnostics(),
|
||||||
|
moveEvaluation.diagnostics(),
|
||||||
|
deleteEvaluation.diagnostics())));
|
||||||
}
|
}
|
||||||
|
|
||||||
public PackerRegisterAssetEvaluation evaluateRegister(
|
public PackerRegisterAssetEvaluation evaluateRegister(
|
||||||
@ -153,6 +167,98 @@ public final class PackerAssetActionReadService {
|
|||||||
return new PackerDeleteAssetEvaluation(resolved, diagnostics, true, null);
|
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) {
|
private String firstBlockingReason(List<PackerDiagnostic> diagnostics, String fallback) {
|
||||||
return diagnostics.stream()
|
return diagnostics.stream()
|
||||||
.filter(PackerDiagnostic::blocking)
|
.filter(PackerDiagnostic::blocking)
|
||||||
@ -162,11 +268,25 @@ public final class PackerAssetActionReadService {
|
|||||||
.orElse(fallback);
|
.orElse(fallback);
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<PackerDiagnostic> combinedDiagnostics(
|
@SafeVarargs
|
||||||
List<PackerDiagnostic> left,
|
private List<PackerDiagnostic> combinedDiagnostics(List<PackerDiagnostic>... groups) {
|
||||||
List<PackerDiagnostic> right) {
|
final List<PackerDiagnostic> combined = new ArrayList<>();
|
||||||
final List<PackerDiagnostic> combined = new ArrayList<>(left);
|
for (List<PackerDiagnostic> group : groups) {
|
||||||
combined.addAll(right);
|
combined.addAll(group);
|
||||||
|
}
|
||||||
return combined;
|
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();
|
.toList();
|
||||||
return new PackerRuntimeSnapshot(generation, updatedRegistry, updatedAssets);
|
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.GetAssetActionsRequest;
|
||||||
import p.packer.messages.GetAssetDetailsRequest;
|
import p.packer.messages.GetAssetDetailsRequest;
|
||||||
import p.packer.messages.ListAssetsRequest;
|
import p.packer.messages.ListAssetsRequest;
|
||||||
|
import p.packer.messages.MoveAssetRequest;
|
||||||
|
import p.packer.messages.MoveAssetResult;
|
||||||
import p.packer.messages.RegisterAssetRequest;
|
import p.packer.messages.RegisterAssetRequest;
|
||||||
import p.packer.messages.RegisterAssetResult;
|
import p.packer.messages.RegisterAssetResult;
|
||||||
import p.packer.messages.assets.AssetAction;
|
import p.packer.messages.assets.AssetAction;
|
||||||
@ -201,15 +203,16 @@ final class FileSystemPackerWorkspaceServiceTest {
|
|||||||
AssetReference.forRelativeAssetRoot("orphans/ui_sounds")));
|
AssetReference.forRelativeAssetRoot("orphans/ui_sounds")));
|
||||||
|
|
||||||
assertEquals(PackerOperationStatus.SUCCESS, result.status());
|
assertEquals(PackerOperationStatus.SUCCESS, result.status());
|
||||||
assertEquals(2, result.actions().size());
|
assertEquals(3, result.actions().size());
|
||||||
assertEquals(AssetAction.REGISTER, result.actions().get(0).action());
|
assertEquals(AssetAction.REGISTER, result.actions().get(0).action());
|
||||||
assertTrue(result.actions().get(0).visible());
|
assertTrue(result.actions().get(0).visible());
|
||||||
assertTrue(result.actions().get(0).enabled());
|
assertTrue(result.actions().get(0).enabled());
|
||||||
assertNull(result.actions().get(0).reason());
|
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).visible());
|
||||||
assertTrue(result.actions().get(1).enabled());
|
assertTrue(result.actions().get(1).enabled());
|
||||||
assertNull(result.actions().get(1).reason());
|
assertNull(result.actions().get(1).reason());
|
||||||
|
assertEquals(AssetAction.DELETE, result.actions().get(2).action());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -222,9 +225,11 @@ final class FileSystemPackerWorkspaceServiceTest {
|
|||||||
AssetReference.forAssetId(1)));
|
AssetReference.forAssetId(1)));
|
||||||
|
|
||||||
assertEquals(PackerOperationStatus.SUCCESS, result.status());
|
assertEquals(PackerOperationStatus.SUCCESS, result.status());
|
||||||
assertEquals(1, result.actions().size());
|
assertEquals(2, result.actions().size());
|
||||||
assertEquals(AssetAction.DELETE, result.actions().getFirst().action());
|
assertEquals(AssetAction.MOVE, result.actions().get(0).action());
|
||||||
assertTrue(result.actions().getFirst().enabled());
|
assertTrue(result.actions().get(0).enabled());
|
||||||
|
assertEquals(AssetAction.DELETE, result.actions().get(1).action());
|
||||||
|
assertTrue(result.actions().get(1).enabled());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -270,8 +275,9 @@ 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()));
|
||||||
assertEquals(1, actionsResult.actions().size());
|
assertEquals(2, actionsResult.actions().size());
|
||||||
assertEquals(AssetAction.DELETE, actionsResult.actions().getFirst().action());
|
assertEquals(AssetAction.MOVE, actionsResult.actions().get(0).action());
|
||||||
|
assertEquals(AssetAction.DELETE, actionsResult.actions().get(1).action());
|
||||||
assertEquals(1, loader.loadCount());
|
assertEquals(1, loader.loadCount());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -285,12 +291,13 @@ final class FileSystemPackerWorkspaceServiceTest {
|
|||||||
AssetReference.forRelativeAssetRoot("bad")));
|
AssetReference.forRelativeAssetRoot("bad")));
|
||||||
|
|
||||||
assertEquals(PackerOperationStatus.PARTIAL, result.status());
|
assertEquals(PackerOperationStatus.PARTIAL, result.status());
|
||||||
assertEquals(2, result.actions().size());
|
assertEquals(3, result.actions().size());
|
||||||
assertEquals(AssetAction.REGISTER, result.actions().get(0).action());
|
assertEquals(AssetAction.REGISTER, result.actions().get(0).action());
|
||||||
assertFalse(result.actions().get(0).enabled());
|
assertFalse(result.actions().get(0).enabled());
|
||||||
assertNotNull(result.actions().get(0).reason());
|
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());
|
assertTrue(result.actions().get(1).enabled());
|
||||||
|
assertEquals(AssetAction.DELETE, result.actions().get(2).action());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -350,12 +357,13 @@ 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(2, actions.actions().size());
|
assertEquals(3, actions.actions().size());
|
||||||
assertEquals(AssetAction.REGISTER, actions.actions().get(0).action());
|
assertEquals(AssetAction.REGISTER, actions.actions().get(0).action());
|
||||||
assertFalse(actions.actions().get(0).enabled());
|
assertFalse(actions.actions().get(0).enabled());
|
||||||
assertNotNull(actions.actions().get(0).reason());
|
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());
|
assertTrue(actions.actions().get(1).enabled());
|
||||||
|
assertEquals(AssetAction.DELETE, actions.actions().get(2).action());
|
||||||
|
|
||||||
final RegisterAssetResult registerResult = service.registerAsset(new RegisterAssetRequest(
|
final RegisterAssetResult registerResult = service.registerAsset(new RegisterAssetRequest(
|
||||||
project(projectRoot),
|
project(projectRoot),
|
||||||
@ -393,6 +401,126 @@ final class FileSystemPackerWorkspaceServiceTest {
|
|||||||
assertTrue(listResult.assets().isEmpty());
|
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
|
@Test
|
||||||
void deletesUnregisteredAssetJsonAndRemovesItFromSnapshot() throws Exception {
|
void deletesUnregisteredAssetJsonAndRemovesItFromSnapshot() throws Exception {
|
||||||
final Path projectRoot = copyFixture("workspaces/orphan-valid", tempDir.resolve("delete-unregistered"));
|
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_DESTINATION_DESCRIPTION("assets.relocateWizard.step.destination.description"),
|
||||||
ASSETS_RELOCATE_WIZARD_STEP_SUMMARY_TITLE("assets.relocateWizard.step.summary.title"),
|
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_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_CURRENT_ROOT("assets.relocateWizard.label.currentRoot"),
|
||||||
ASSETS_RELOCATE_WIZARD_LABEL_DESTINATION_PARENT("assets.relocateWizard.label.destinationParent"),
|
ASSETS_RELOCATE_WIZARD_LABEL_DESTINATION_PARENT("assets.relocateWizard.label.destinationParent"),
|
||||||
ASSETS_RELOCATE_WIZARD_LABEL_DESTINATION_NAME("assets.relocateWizard.label.destinationName"),
|
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_NOTE("assets.relocateWizard.note"),
|
||||||
ASSETS_RELOCATE_WIZARD_BUTTON_CONFIRM("assets.relocateWizard.button.confirm"),
|
ASSETS_RELOCATE_WIZARD_BUTTON_CONFIRM("assets.relocateWizard.button.confirm"),
|
||||||
ASSETS_RELOCATE_WIZARD_BUTTON_PREVIEW("assets.relocateWizard.button.preview"),
|
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"),
|
WORKSPACE_DEBUG("workspace.debug"),
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -119,7 +119,7 @@ public final class AssetWorkspace extends Workspace {
|
|||||||
}
|
}
|
||||||
AddAssetWizard.showAndWait(root.getScene().getWindow(), projectReference).ifPresent(assetReference -> {
|
AddAssetWizard.showAndWait(root.getScene().getWindow(), projectReference).ifPresent(assetReference -> {
|
||||||
workspaceEventBus.publish(new StudioAssetsRefreshRequestedEvent(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.HBox;
|
||||||
import javafx.scene.layout.Priority;
|
import javafx.scene.layout.Priority;
|
||||||
import javafx.scene.layout.VBox;
|
import javafx.scene.layout.VBox;
|
||||||
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.*;
|
||||||
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.studio.Container;
|
import p.studio.Container;
|
||||||
import p.studio.events.StudioWorkspaceEventBus;
|
import p.studio.events.StudioWorkspaceEventBus;
|
||||||
import p.studio.projects.ProjectReference;
|
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.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.assets.wizards.DeleteAssetDialog;
|
||||||
|
import p.studio.workspaces.assets.wizards.MoveAssetWizard;
|
||||||
import p.studio.workspaces.framework.StudioEventAware;
|
import p.studio.workspaces.framework.StudioEventAware;
|
||||||
import p.studio.workspaces.framework.StudioEventBindings;
|
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 -> {
|
eventBindings.listen(workspaceBus, StudioAssetsWorkspaceSelectionRequestedEvent.class).handle(event -> {
|
||||||
if (!isCurrentSelection(event.assetReference())) {
|
if (event.forceUpdate() || !isCurrentSelection(event.assetReference())) {
|
||||||
loadSelection(event.assetReference());
|
loadSelection(event.assetReference());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -309,10 +303,25 @@ public final class AssetDetailsControl extends VBox implements StudioEventAware
|
|||||||
renderActions();
|
renderActions();
|
||||||
Container.backgroundTasks().submit(() -> registerSelectedAsset(viewState.selectedAssetReference()));
|
Container.backgroundTasks().submit(() -> registerSelectedAsset(viewState.selectedAssetReference()));
|
||||||
}
|
}
|
||||||
|
case MOVE -> openMoveWizard();
|
||||||
case DELETE -> confirmDeleteAction();
|
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() {
|
private void confirmDeleteAction() {
|
||||||
if (viewState.selectedAssetDetails() == null || getScene() == null) {
|
if (viewState.selectedAssetDetails() == null || getScene() == null) {
|
||||||
return;
|
return;
|
||||||
@ -346,7 +355,7 @@ public final class AssetDetailsControl extends VBox implements StudioEventAware
|
|||||||
if (result.status() == p.packer.messages.PackerOperationStatus.SUCCESS && result.assetReference() != null) {
|
if (result.status() == p.packer.messages.PackerOperationStatus.SUCCESS && result.assetReference() != null) {
|
||||||
actionFeedbackMessage = null;
|
actionFeedbackMessage = null;
|
||||||
workspaceBus.publish(new StudioAssetsRefreshRequestedEvent(result.assetReference()));
|
workspaceBus.publish(new StudioAssetsRefreshRequestedEvent(result.assetReference()));
|
||||||
workspaceBus.publish(new StudioAssetsWorkspaceSelectionRequestedEvent(result.assetReference()));
|
workspaceBus.publish(new StudioAssetsWorkspaceSelectionRequestedEvent(result.assetReference(), false));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
actionFeedbackMessage = Objects.requireNonNullElse(result.summary(), "Unable to register asset.");
|
actionFeedbackMessage = Objects.requireNonNullElse(result.summary(), "Unable to register asset.");
|
||||||
|
|||||||
@ -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 MOVE -> Container.i18n().text(I18n.ASSETS_ACTION_RELOCATE);
|
||||||
case DELETE -> Container.i18n().text(I18n.ASSETS_ACTION_DELETE);
|
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()));
|
getStyleClass().setAll("assets-workspace-asset-row", assetRowToneClass(summary.assetFamily()));
|
||||||
getChildren().setAll(createTopLine(), createPathLabel());
|
getChildren().setAll(createTopLine(), createPathLabel());
|
||||||
setOnMouseClicked(event -> workspaceBus.publish(
|
setOnMouseClicked(event -> workspaceBus.publish(
|
||||||
new StudioAssetsWorkspaceSelectionRequestedEvent(summary.assetReference())));
|
new StudioAssetsWorkspaceSelectionRequestedEvent(summary.assetReference(), false)));
|
||||||
}
|
}
|
||||||
|
|
||||||
private HBox createTopLine() {
|
private HBox createTopLine() {
|
||||||
|
|||||||
@ -6,7 +6,8 @@ import p.studio.events.StudioEvent;
|
|||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
public record StudioAssetsWorkspaceSelectionRequestedEvent(
|
public record StudioAssetsWorkspaceSelectionRequestedEvent(
|
||||||
AssetReference assetReference) implements StudioEvent {
|
AssetReference assetReference,
|
||||||
|
boolean forceUpdate) implements StudioEvent {
|
||||||
public StudioAssetsWorkspaceSelectionRequestedEvent {
|
public StudioAssetsWorkspaceSelectionRequestedEvent {
|
||||||
Objects.requireNonNull(assetReference, "assetReference");
|
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.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=Move
|
||||||
assets.action.remove=Remove
|
assets.action.remove=Remove
|
||||||
assets.mutation.previewTitle=Preview: {0}
|
assets.mutation.previewTitle=Preview: {0}
|
||||||
assets.mutation.section.changes=Changes
|
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.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.title=Review Relocation
|
||||||
assets.relocateWizard.step.summary.description=Review the mutation impact before confirming this 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.currentRoot=Current Asset Root
|
||||||
assets.relocateWizard.label.destinationParent=Destination Parent
|
assets.relocateWizard.label.destinationParent=Destination Parent
|
||||||
assets.relocateWizard.label.destinationName=Destination Folder Name
|
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.note=OK applies this relocation immediately. Use Back if you need to change the destination.
|
||||||
assets.relocateWizard.button.confirm=OK
|
assets.relocateWizard.button.confirm=OK
|
||||||
assets.relocateWizard.button.preview=Preview Relocation
|
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
|
workspace.debug=Debug
|
||||||
|
|||||||
@ -3,6 +3,591 @@
|
|||||||
"message" : "8 assets loaded",
|
"message" : "8 assets loaded",
|
||||||
"severity" : "SUCCESS",
|
"severity" : "SUCCESS",
|
||||||
"sticky" : false
|
"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",
|
"source" : "Assets",
|
||||||
"message" : "Asset scan diagnostics updated.",
|
"message" : "Asset scan diagnostics updated.",
|
||||||
@ -1913,354 +2498,4 @@
|
|||||||
"message" : "Discovered asset: Novo Asset",
|
"message" : "Discovered asset: Novo Asset",
|
||||||
"severity" : "INFO",
|
"severity" : "INFO",
|
||||||
"sticky" : false
|
"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,
|
"schema_version" : 1,
|
||||||
"next_asset_id" : 14,
|
"next_asset_id" : 15,
|
||||||
"assets" : [ {
|
"assets" : [ {
|
||||||
"asset_id" : 3,
|
"asset_id" : 3,
|
||||||
"asset_uuid" : "21953cb8-4101-4790-9e5e-d95f5fbc9b5a",
|
"asset_uuid" : "21953cb8-4101-4790-9e5e-d95f5fbc9b5a",
|
||||||
@ -14,7 +14,7 @@
|
|||||||
}, {
|
}, {
|
||||||
"asset_id" : 8,
|
"asset_id" : 8,
|
||||||
"asset_uuid" : "9a7386e7-6f0e-4e4c-9919-0de71e0b7031",
|
"asset_uuid" : "9a7386e7-6f0e-4e4c-9919-0de71e0b7031",
|
||||||
"root" : "sound",
|
"root" : "ui/sound",
|
||||||
"included_in_build" : true
|
"included_in_build" : true
|
||||||
}, {
|
}, {
|
||||||
"asset_id" : 9,
|
"asset_id" : 9,
|
||||||
@ -36,5 +36,10 @@
|
|||||||
"asset_uuid" : "4d9847b0-5a23-421f-8b78-bf3909ca2281",
|
"asset_uuid" : "4d9847b0-5a23-421f-8b78-bf3909ca2281",
|
||||||
"root" : "recovered/one-more-atlas",
|
"root" : "recovered/one-more-atlas",
|
||||||
"included_in_build" : true
|
"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