mirror of
https://github.com/imgfloat/server.git
synced 2026-02-05 03:39:26 +00:00
Add fragile layering system
This commit is contained in:
@@ -32,6 +32,9 @@ public class Asset {
|
|||||||
@Column(name = "updated_at", nullable = false)
|
@Column(name = "updated_at", nullable = false)
|
||||||
private Instant updatedAt;
|
private Instant updatedAt;
|
||||||
|
|
||||||
|
@Column(name = "display_order")
|
||||||
|
private Integer displayOrder;
|
||||||
|
|
||||||
public Asset() {}
|
public Asset() {}
|
||||||
|
|
||||||
public Asset(String broadcaster, AssetType assetType) {
|
public Asset(String broadcaster, AssetType assetType) {
|
||||||
@@ -57,6 +60,9 @@ public class Asset {
|
|||||||
if (this.assetType == null) {
|
if (this.assetType == null) {
|
||||||
this.assetType = AssetType.OTHER;
|
this.assetType = AssetType.OTHER;
|
||||||
}
|
}
|
||||||
|
if (this.displayOrder != null && this.displayOrder < 1) {
|
||||||
|
this.displayOrder = 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getId() {
|
public String getId() {
|
||||||
@@ -95,6 +101,14 @@ public class Asset {
|
|||||||
this.updatedAt = updatedAt;
|
this.updatedAt = updatedAt;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Integer getDisplayOrder() {
|
||||||
|
return displayOrder;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDisplayOrder(Integer displayOrder) {
|
||||||
|
this.displayOrder = displayOrder;
|
||||||
|
}
|
||||||
|
|
||||||
private static String normalize(String value) {
|
private static String normalize(String value) {
|
||||||
return value == null ? null : value.toLowerCase(Locale.ROOT);
|
return value == null ? null : value.toLowerCase(Locale.ROOT);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ public record AssetPatch(
|
|||||||
Double rotation,
|
Double rotation,
|
||||||
Double speed,
|
Double speed,
|
||||||
Boolean muted,
|
Boolean muted,
|
||||||
Integer zIndex,
|
Integer order,
|
||||||
Boolean hidden,
|
Boolean hidden,
|
||||||
Boolean audioLoop,
|
Boolean audioLoop,
|
||||||
Integer audioDelayMillis,
|
Integer audioDelayMillis,
|
||||||
@@ -37,7 +37,7 @@ public record AssetPatch(
|
|||||||
request.getRotation() != null ? changed(before.rotation(), asset.getRotation()) : null,
|
request.getRotation() != null ? changed(before.rotation(), asset.getRotation()) : null,
|
||||||
request.getSpeed() != null ? changed(before.speed(), asset.getSpeed()) : null,
|
request.getSpeed() != null ? changed(before.speed(), asset.getSpeed()) : null,
|
||||||
request.getMuted() != null ? changed(before.muted(), asset.isMuted()) : null,
|
request.getMuted() != null ? changed(before.muted(), asset.isMuted()) : null,
|
||||||
request.getZIndex() != null ? changed(before.zIndex(), asset.getZIndex()) : null,
|
request.getOrder() != null ? changed(before.order(), request.getOrder()) : null,
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
@@ -112,7 +112,7 @@ public record AssetPatch(
|
|||||||
double rotation,
|
double rotation,
|
||||||
double speed,
|
double speed,
|
||||||
boolean muted,
|
boolean muted,
|
||||||
int zIndex,
|
int order,
|
||||||
double audioVolume
|
double audioVolume
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
|
|||||||
@@ -23,7 +23,6 @@ public record AssetView(
|
|||||||
String originalMediaType,
|
String originalMediaType,
|
||||||
AssetType assetType,
|
AssetType assetType,
|
||||||
List<ScriptAssetAttachmentView> scriptAttachments,
|
List<ScriptAssetAttachmentView> scriptAttachments,
|
||||||
Integer zIndex,
|
|
||||||
Boolean audioLoop,
|
Boolean audioLoop,
|
||||||
Integer audioDelayMillis,
|
Integer audioDelayMillis,
|
||||||
Double audioSpeed,
|
Double audioSpeed,
|
||||||
@@ -56,7 +55,6 @@ public record AssetView(
|
|||||||
visual.getOriginalMediaType(),
|
visual.getOriginalMediaType(),
|
||||||
asset.getAssetType(),
|
asset.getAssetType(),
|
||||||
null,
|
null,
|
||||||
visual.getZIndex(),
|
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
@@ -90,7 +88,6 @@ public record AssetView(
|
|||||||
audio.getOriginalMediaType(),
|
audio.getOriginalMediaType(),
|
||||||
asset.getAssetType(),
|
asset.getAssetType(),
|
||||||
null,
|
null,
|
||||||
null,
|
|
||||||
audio.isAudioLoop(),
|
audio.isAudioLoop(),
|
||||||
audio.getAudioDelayMillis(),
|
audio.getAudioDelayMillis(),
|
||||||
audio.getAudioSpeed(),
|
audio.getAudioSpeed(),
|
||||||
@@ -126,7 +123,6 @@ public record AssetView(
|
|||||||
script.getOriginalMediaType(),
|
script.getOriginalMediaType(),
|
||||||
asset.getAssetType(),
|
asset.getAssetType(),
|
||||||
script.getAttachments(),
|
script.getAttachments(),
|
||||||
script.getZIndex(),
|
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
|
|||||||
@@ -33,9 +33,6 @@ public class ScriptAsset {
|
|||||||
@Column(name = "source_file_id")
|
@Column(name = "source_file_id")
|
||||||
private String sourceFileId;
|
private String sourceFileId;
|
||||||
|
|
||||||
@Column(name = "z_index")
|
|
||||||
private Integer zIndex;
|
|
||||||
|
|
||||||
@Transient
|
@Transient
|
||||||
private List<ScriptAssetAttachmentView> attachments = List.of();
|
private List<ScriptAssetAttachmentView> attachments = List.of();
|
||||||
|
|
||||||
@@ -52,9 +49,6 @@ public class ScriptAsset {
|
|||||||
if (this.name == null || this.name.isBlank()) {
|
if (this.name == null || this.name.isBlank()) {
|
||||||
this.name = this.id;
|
this.name = this.id;
|
||||||
}
|
}
|
||||||
if (this.zIndex == null || this.zIndex < 1) {
|
|
||||||
this.zIndex = 1;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getId() {
|
public String getId() {
|
||||||
@@ -121,14 +115,6 @@ public class ScriptAsset {
|
|||||||
this.sourceFileId = sourceFileId;
|
this.sourceFileId = sourceFileId;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Integer getZIndex() {
|
|
||||||
return zIndex == null ? 1 : Math.max(1, zIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setZIndex(Integer zIndex) {
|
|
||||||
this.zIndex = zIndex == null ? null : Math.max(1, zIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<ScriptAssetAttachmentView> getAttachments() {
|
public List<ScriptAssetAttachmentView> getAttachments() {
|
||||||
return attachments == null ? List.of() : attachments;
|
return attachments == null ? List.of() : attachments;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,8 +24,8 @@ public class TransformRequest {
|
|||||||
|
|
||||||
private Boolean muted;
|
private Boolean muted;
|
||||||
|
|
||||||
@Positive(message = "zIndex must be at least 1")
|
@Positive(message = "Order must be at least 1")
|
||||||
private Integer zIndex;
|
private Integer order;
|
||||||
|
|
||||||
private Boolean audioLoop;
|
private Boolean audioLoop;
|
||||||
|
|
||||||
@@ -100,12 +100,12 @@ public class TransformRequest {
|
|||||||
this.muted = muted;
|
this.muted = muted;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Integer getZIndex() {
|
public Integer getOrder() {
|
||||||
return zIndex;
|
return order;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setZIndex(Integer zIndex) {
|
public void setOrder(Integer order) {
|
||||||
this.zIndex = zIndex;
|
this.order = order;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Boolean getAudioLoop() {
|
public Boolean getAudioLoop() {
|
||||||
|
|||||||
@@ -28,7 +28,6 @@ public class VisualAsset {
|
|||||||
private Boolean muted;
|
private Boolean muted;
|
||||||
private String mediaType;
|
private String mediaType;
|
||||||
private String originalMediaType;
|
private String originalMediaType;
|
||||||
private Integer zIndex;
|
|
||||||
private Double audioVolume;
|
private Double audioVolume;
|
||||||
private boolean hidden;
|
private boolean hidden;
|
||||||
|
|
||||||
@@ -44,7 +43,6 @@ public class VisualAsset {
|
|||||||
this.rotation = 0;
|
this.rotation = 0;
|
||||||
this.speed = 1.0;
|
this.speed = 1.0;
|
||||||
this.muted = false;
|
this.muted = false;
|
||||||
this.zIndex = 1;
|
|
||||||
this.audioVolume = 1.0;
|
this.audioVolume = 1.0;
|
||||||
this.hidden = true;
|
this.hidden = true;
|
||||||
}
|
}
|
||||||
@@ -58,9 +56,6 @@ public class VisualAsset {
|
|||||||
if (this.muted == null) {
|
if (this.muted == null) {
|
||||||
this.muted = Boolean.FALSE;
|
this.muted = Boolean.FALSE;
|
||||||
}
|
}
|
||||||
if (this.zIndex == null || this.zIndex < 1) {
|
|
||||||
this.zIndex = 1;
|
|
||||||
}
|
|
||||||
if (this.audioVolume == null) {
|
if (this.audioVolume == null) {
|
||||||
this.audioVolume = 1.0;
|
this.audioVolume = 1.0;
|
||||||
}
|
}
|
||||||
@@ -165,14 +160,6 @@ public class VisualAsset {
|
|||||||
this.originalMediaType = originalMediaType;
|
this.originalMediaType = originalMediaType;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Integer getZIndex() {
|
|
||||||
return zIndex == null ? 1 : Math.max(1, zIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setZIndex(Integer zIndex) {
|
|
||||||
this.zIndex = zIndex == null ? null : Math.max(1, zIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
public double getAudioVolume() {
|
public double getAudioVolume() {
|
||||||
return audioVolume == null ? 1.0 : Math.max(0.0, Math.min(1.0, audioVolume));
|
return audioVolume == null ? 1.0 : Math.max(0.0, Math.min(1.0, audioVolume));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -189,10 +189,11 @@ public class ChannelDirectoryService {
|
|||||||
.stream()
|
.stream()
|
||||||
.map((visual) -> {
|
.map((visual) -> {
|
||||||
Asset asset = assetById.get(visual.getId());
|
Asset asset = assetById.get(visual.getId());
|
||||||
return asset == null ? null : AssetView.fromVisual(normalized, asset, visual);
|
return asset == null ? null : Map.entry(asset, visual);
|
||||||
})
|
})
|
||||||
.filter(Objects::nonNull)
|
.filter(Objects::nonNull)
|
||||||
.sorted(Comparator.comparingInt(AssetView::zIndex).reversed())
|
.sorted(Comparator.comparingInt((Map.Entry<Asset, VisualAsset> entry) -> displayOrderValue(entry.getKey())).reversed())
|
||||||
|
.map((entry) -> AssetView.fromVisual(normalized, entry.getKey(), entry.getValue()))
|
||||||
.toList();
|
.toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -318,6 +319,13 @@ public class ChannelDirectoryService {
|
|||||||
boolean isCode = isCodeMediaType(optimized.mediaType()) || isCodeMediaType(mediaType);
|
boolean isCode = isCodeMediaType(optimized.mediaType()) || isCodeMediaType(mediaType);
|
||||||
AssetType assetType = AssetType.fromMediaType(optimized.mediaType(), mediaType);
|
AssetType assetType = AssetType.fromMediaType(optimized.mediaType(), mediaType);
|
||||||
Asset asset = new Asset(channel.getBroadcaster(), assetType);
|
Asset asset = new Asset(channel.getBroadcaster(), assetType);
|
||||||
|
if (!isAudio && isCode) {
|
||||||
|
asset.setDisplayOrder(nextDisplayOrder(channel.getBroadcaster(), AssetType.SCRIPT));
|
||||||
|
} else if (!isAudio) {
|
||||||
|
asset.setDisplayOrder(
|
||||||
|
nextDisplayOrder(channel.getBroadcaster(), AssetType.IMAGE, AssetType.VIDEO, AssetType.MODEL, AssetType.OTHER)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
assetStorageService.storeAsset(
|
assetStorageService.storeAsset(
|
||||||
channel.getBroadcaster(),
|
channel.getBroadcaster(),
|
||||||
@@ -340,7 +348,6 @@ public class ChannelDirectoryService {
|
|||||||
script.setMediaType(optimized.mediaType());
|
script.setMediaType(optimized.mediaType());
|
||||||
script.setOriginalMediaType(mediaType);
|
script.setOriginalMediaType(mediaType);
|
||||||
script.setSourceFileId(asset.getId());
|
script.setSourceFileId(asset.getId());
|
||||||
script.setZIndex(nextScriptZIndex(channel.getBroadcaster()));
|
|
||||||
script.setAttachments(List.of());
|
script.setAttachments(List.of());
|
||||||
scriptAssetRepository.save(script);
|
scriptAssetRepository.save(script);
|
||||||
ScriptAssetFile sourceFile = new ScriptAssetFile(asset.getBroadcaster(), AssetType.SCRIPT);
|
ScriptAssetFile sourceFile = new ScriptAssetFile(asset.getBroadcaster(), AssetType.SCRIPT);
|
||||||
@@ -358,7 +365,6 @@ public class ChannelDirectoryService {
|
|||||||
visual.setOriginalMediaType(mediaType);
|
visual.setOriginalMediaType(mediaType);
|
||||||
visual.setMediaType(optimized.mediaType());
|
visual.setMediaType(optimized.mediaType());
|
||||||
visual.setMuted(optimized.mediaType().startsWith("video/"));
|
visual.setMuted(optimized.mediaType().startsWith("video/"));
|
||||||
visual.setZIndex(nextZIndex(channel.getBroadcaster()));
|
|
||||||
assetStorageService.storePreview(channel.getBroadcaster(), asset.getId(), optimized.previewBytes());
|
assetStorageService.storePreview(channel.getBroadcaster(), asset.getId(), optimized.previewBytes());
|
||||||
visual.setPreview(optimized.previewBytes() != null ? asset.getId() + ".png" : "");
|
visual.setPreview(optimized.previewBytes() != null ? asset.getId() + ".png" : "");
|
||||||
visualAssetRepository.save(visual);
|
visualAssetRepository.save(visual);
|
||||||
@@ -383,6 +389,7 @@ public class ChannelDirectoryService {
|
|||||||
enforceUploadLimit(bytes.length);
|
enforceUploadLimit(bytes.length);
|
||||||
|
|
||||||
Asset asset = new Asset(channel.getBroadcaster(), AssetType.SCRIPT);
|
Asset asset = new Asset(channel.getBroadcaster(), AssetType.SCRIPT);
|
||||||
|
asset.setDisplayOrder(nextDisplayOrder(channel.getBroadcaster(), AssetType.SCRIPT));
|
||||||
ScriptAssetFile sourceFile = new ScriptAssetFile(asset.getBroadcaster(), AssetType.SCRIPT);
|
ScriptAssetFile sourceFile = new ScriptAssetFile(asset.getBroadcaster(), AssetType.SCRIPT);
|
||||||
sourceFile.setId(asset.getId());
|
sourceFile.setId(asset.getId());
|
||||||
sourceFile.setMediaType(DEFAULT_CODE_MEDIA_TYPE);
|
sourceFile.setMediaType(DEFAULT_CODE_MEDIA_TYPE);
|
||||||
@@ -407,7 +414,6 @@ public class ChannelDirectoryService {
|
|||||||
script.setSourceFileId(sourceFile.getId());
|
script.setSourceFileId(sourceFile.getId());
|
||||||
script.setDescription(normalizeDescription(request.getDescription()));
|
script.setDescription(normalizeDescription(request.getDescription()));
|
||||||
script.setPublic(Boolean.TRUE.equals(request.getIsPublic()));
|
script.setPublic(Boolean.TRUE.equals(request.getIsPublic()));
|
||||||
script.setZIndex(nextScriptZIndex(channel.getBroadcaster()));
|
|
||||||
script.setAttachments(List.of());
|
script.setAttachments(List.of());
|
||||||
scriptAssetRepository.save(script);
|
scriptAssetRepository.save(script);
|
||||||
AssetView view = AssetView.fromScript(channel.getBroadcaster(), asset, script);
|
AssetView view = AssetView.fromScript(channel.getBroadcaster(), asset, script);
|
||||||
@@ -787,6 +793,7 @@ public class ChannelDirectoryService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Asset asset = new Asset(targetBroadcaster, AssetType.SCRIPT);
|
Asset asset = new Asset(targetBroadcaster, AssetType.SCRIPT);
|
||||||
|
asset.setDisplayOrder(nextDisplayOrder(targetBroadcaster, AssetType.SCRIPT));
|
||||||
ScriptAssetFile sourceFile = new ScriptAssetFile(asset.getBroadcaster(), AssetType.SCRIPT);
|
ScriptAssetFile sourceFile = new ScriptAssetFile(asset.getBroadcaster(), AssetType.SCRIPT);
|
||||||
sourceFile.setId(asset.getId());
|
sourceFile.setId(asset.getId());
|
||||||
sourceFile.setMediaType(sourceContent.mediaType());
|
sourceFile.setMediaType(sourceContent.mediaType());
|
||||||
@@ -811,7 +818,6 @@ public class ChannelDirectoryService {
|
|||||||
script.setOriginalMediaType(sourceContent.mediaType());
|
script.setOriginalMediaType(sourceContent.mediaType());
|
||||||
script.setSourceFileId(sourceFile.getId());
|
script.setSourceFileId(sourceFile.getId());
|
||||||
script.setLogoFileId(sourceScript.getLogoFileId());
|
script.setLogoFileId(sourceScript.getLogoFileId());
|
||||||
script.setZIndex(nextScriptZIndex(targetBroadcaster));
|
|
||||||
script.setAttachments(List.of());
|
script.setAttachments(List.of());
|
||||||
scriptAssetRepository.save(script);
|
scriptAssetRepository.save(script);
|
||||||
|
|
||||||
@@ -858,6 +864,7 @@ public class ChannelDirectoryService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Asset asset = new Asset(targetBroadcaster, AssetType.SCRIPT);
|
Asset asset = new Asset(targetBroadcaster, AssetType.SCRIPT);
|
||||||
|
asset.setDisplayOrder(nextDisplayOrder(targetBroadcaster, AssetType.SCRIPT));
|
||||||
ScriptAssetFile sourceFile = new ScriptAssetFile(asset.getBroadcaster(), AssetType.SCRIPT);
|
ScriptAssetFile sourceFile = new ScriptAssetFile(asset.getBroadcaster(), AssetType.SCRIPT);
|
||||||
sourceFile.setId(asset.getId());
|
sourceFile.setId(asset.getId());
|
||||||
sourceFile.setMediaType(sourceContent.mediaType());
|
sourceFile.setMediaType(sourceContent.mediaType());
|
||||||
@@ -881,7 +888,6 @@ public class ChannelDirectoryService {
|
|||||||
script.setMediaType(sourceContent.mediaType());
|
script.setMediaType(sourceContent.mediaType());
|
||||||
script.setOriginalMediaType(sourceContent.mediaType());
|
script.setOriginalMediaType(sourceContent.mediaType());
|
||||||
script.setSourceFileId(sourceFile.getId());
|
script.setSourceFileId(sourceFile.getId());
|
||||||
script.setZIndex(nextScriptZIndex(targetBroadcaster));
|
|
||||||
script.setAttachments(List.of());
|
script.setAttachments(List.of());
|
||||||
scriptAssetRepository.save(script);
|
scriptAssetRepository.save(script);
|
||||||
|
|
||||||
@@ -1009,14 +1015,19 @@ public class ChannelDirectoryService {
|
|||||||
ScriptAsset script = scriptAssetRepository
|
ScriptAsset script = scriptAssetRepository
|
||||||
.findById(asset.getId())
|
.findById(asset.getId())
|
||||||
.orElseThrow(() -> new ResponseStatusException(BAD_REQUEST, "Asset is not a script"));
|
.orElseThrow(() -> new ResponseStatusException(BAD_REQUEST, "Asset is not a script"));
|
||||||
int beforeZIndex = script.getZIndex();
|
Integer beforeOrder = asset.getDisplayOrder();
|
||||||
if (req.getZIndex() != null) {
|
List<Asset> orderUpdates = List.of();
|
||||||
if (req.getZIndex() < 1) {
|
if (req.getOrder() != null) {
|
||||||
throw new ResponseStatusException(BAD_REQUEST, "zIndex must be >= 1");
|
if (req.getOrder() < 1) {
|
||||||
|
throw new ResponseStatusException(BAD_REQUEST, "Order must be >= 1");
|
||||||
}
|
}
|
||||||
script.setZIndex(req.getZIndex());
|
orderUpdates = updateDisplayOrder(
|
||||||
scriptAssetRepository.save(script);
|
normalized,
|
||||||
if (beforeZIndex != script.getZIndex()) {
|
asset,
|
||||||
|
req.getOrder(),
|
||||||
|
EnumSet.of(AssetType.SCRIPT)
|
||||||
|
);
|
||||||
|
if (!Objects.equals(beforeOrder, asset.getDisplayOrder())) {
|
||||||
AssetPatch patch = new AssetPatch(
|
AssetPatch patch = new AssetPatch(
|
||||||
asset.getId(),
|
asset.getId(),
|
||||||
null,
|
null,
|
||||||
@@ -1026,7 +1037,7 @@ public class ChannelDirectoryService {
|
|||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
script.getZIndex(),
|
asset.getDisplayOrder(),
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
@@ -1038,10 +1049,11 @@ public class ChannelDirectoryService {
|
|||||||
auditLogService.recordEntry(
|
auditLogService.recordEntry(
|
||||||
asset.getBroadcaster(),
|
asset.getBroadcaster(),
|
||||||
actor,
|
actor,
|
||||||
"SCRIPT_LAYER_UPDATED",
|
"SCRIPT_ORDER_UPDATED",
|
||||||
formatScriptTransformDetails(asset.getId(), script.getZIndex())
|
formatScriptTransformDetails(asset.getId(), asset.getDisplayOrder())
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
publishOrderUpdates(broadcaster, asset.getId(), orderUpdates);
|
||||||
}
|
}
|
||||||
script.setAttachments(loadScriptAttachments(normalized, asset.getId(), null));
|
script.setAttachments(loadScriptAttachments(normalized, asset.getId(), null));
|
||||||
return AssetView.fromScript(normalized, asset, script);
|
return AssetView.fromScript(normalized, asset, script);
|
||||||
@@ -1058,17 +1070,28 @@ public class ChannelDirectoryService {
|
|||||||
visual.getRotation(),
|
visual.getRotation(),
|
||||||
visual.getSpeed(),
|
visual.getSpeed(),
|
||||||
visual.isMuted(),
|
visual.isMuted(),
|
||||||
visual.getZIndex(),
|
displayOrderValue(asset),
|
||||||
visual.getAudioVolume()
|
visual.getAudioVolume()
|
||||||
);
|
);
|
||||||
validateVisualTransform(req);
|
validateVisualTransform(req);
|
||||||
|
List<Asset> orderUpdates = List.of();
|
||||||
|
|
||||||
if (req.getX() != null) visual.setX(req.getX());
|
if (req.getX() != null) visual.setX(req.getX());
|
||||||
if (req.getY() != null) visual.setY(req.getY());
|
if (req.getY() != null) visual.setY(req.getY());
|
||||||
if (req.getWidth() != null) visual.setWidth(req.getWidth());
|
if (req.getWidth() != null) visual.setWidth(req.getWidth());
|
||||||
if (req.getHeight() != null) visual.setHeight(req.getHeight());
|
if (req.getHeight() != null) visual.setHeight(req.getHeight());
|
||||||
if (req.getRotation() != null) visual.setRotation(req.getRotation());
|
if (req.getRotation() != null) visual.setRotation(req.getRotation());
|
||||||
if (req.getZIndex() != null) visual.setZIndex(req.getZIndex());
|
if (req.getOrder() != null) {
|
||||||
|
orderUpdates = updateDisplayOrder(
|
||||||
|
normalized,
|
||||||
|
asset,
|
||||||
|
req.getOrder(),
|
||||||
|
EnumSet.of(AssetType.IMAGE, AssetType.VIDEO, AssetType.MODEL, AssetType.OTHER)
|
||||||
|
);
|
||||||
|
if (asset.getDisplayOrder() != null) {
|
||||||
|
req.setOrder(asset.getDisplayOrder());
|
||||||
|
}
|
||||||
|
}
|
||||||
if (req.getSpeed() != null) visual.setSpeed(req.getSpeed());
|
if (req.getSpeed() != null) visual.setSpeed(req.getSpeed());
|
||||||
if (req.getMuted() != null) visual.setMuted(req.getMuted());
|
if (req.getMuted() != null) visual.setMuted(req.getMuted());
|
||||||
if (req.getAudioVolume() != null) visual.setAudioVolume(req.getAudioVolume());
|
if (req.getAudioVolume() != null) visual.setAudioVolume(req.getAudioVolume());
|
||||||
@@ -1086,6 +1109,7 @@ public class ChannelDirectoryService {
|
|||||||
formatVisualTransformDetails(asset.getId(), req)
|
formatVisualTransformDetails(asset.getId(), req)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
publishOrderUpdates(broadcaster, asset.getId(), orderUpdates);
|
||||||
return view;
|
return view;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -1113,9 +1137,9 @@ public class ChannelDirectoryService {
|
|||||||
if (
|
if (
|
||||||
req.getSpeed() != null && (req.getSpeed() < minSpeed || req.getSpeed() > maxSpeed)
|
req.getSpeed() != null && (req.getSpeed() < minSpeed || req.getSpeed() > maxSpeed)
|
||||||
) throw new ResponseStatusException(BAD_REQUEST, "Speed out of range [" + minSpeed + " to " + maxSpeed + "]");
|
) throw new ResponseStatusException(BAD_REQUEST, "Speed out of range [" + minSpeed + " to " + maxSpeed + "]");
|
||||||
if (req.getZIndex() != null && req.getZIndex() < 1) throw new ResponseStatusException(
|
if (req.getOrder() != null && req.getOrder() < 1) throw new ResponseStatusException(
|
||||||
BAD_REQUEST,
|
BAD_REQUEST,
|
||||||
"zIndex must be >= 1"
|
"Order must be >= 1"
|
||||||
);
|
);
|
||||||
if (
|
if (
|
||||||
req.getAudioVolume() != null && (req.getAudioVolume() < minVolume || req.getAudioVolume() > maxVolume)
|
req.getAudioVolume() != null && (req.getAudioVolume() < minVolume || req.getAudioVolume() > maxVolume)
|
||||||
@@ -1580,51 +1604,100 @@ public class ChannelDirectoryService {
|
|||||||
|
|
||||||
return assets
|
return assets
|
||||||
.stream()
|
.stream()
|
||||||
|
.sorted(
|
||||||
|
Comparator.comparingInt((Asset asset) -> displayOrderValue(asset))
|
||||||
|
.reversed()
|
||||||
|
.thenComparing(Asset::getCreatedAt, Comparator.nullsFirst(Comparator.naturalOrder()))
|
||||||
|
)
|
||||||
.map((asset) -> resolveAssetView(broadcaster, asset, visuals, audios, scripts, scriptAttachments))
|
.map((asset) -> resolveAssetView(broadcaster, asset, visuals, audios, scripts, scriptAttachments))
|
||||||
.filter(Objects::nonNull)
|
.filter(Objects::nonNull)
|
||||||
.sorted(
|
|
||||||
Comparator.comparingInt((AssetView view) -> view.zIndex() == null ? Integer.MIN_VALUE : view.zIndex())
|
|
||||||
.reversed()
|
|
||||||
.thenComparing(AssetView::createdAt, Comparator.nullsFirst(Comparator.naturalOrder()))
|
|
||||||
)
|
|
||||||
.toList();
|
.toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
private int nextZIndex(String broadcaster) {
|
private int nextDisplayOrder(String broadcaster, AssetType... types) {
|
||||||
return (
|
return (
|
||||||
visualAssetRepository
|
assetRepository
|
||||||
.findByIdIn(
|
|
||||||
assetsWithType(normalize(broadcaster), AssetType.IMAGE, AssetType.VIDEO, AssetType.MODEL, AssetType.OTHER)
|
|
||||||
)
|
|
||||||
.stream()
|
|
||||||
.mapToInt(VisualAsset::getZIndex)
|
|
||||||
.max()
|
|
||||||
.orElse(0) +
|
|
||||||
1
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private int nextScriptZIndex(String broadcaster) {
|
|
||||||
return (
|
|
||||||
scriptAssetRepository
|
|
||||||
.findByIdIn(assetsWithType(normalize(broadcaster), AssetType.SCRIPT))
|
|
||||||
.stream()
|
|
||||||
.mapToInt(ScriptAsset::getZIndex)
|
|
||||||
.max()
|
|
||||||
.orElse(0) +
|
|
||||||
1
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<String> assetsWithType(String broadcaster, AssetType... types) {
|
|
||||||
Set<AssetType> typeSet = EnumSet.noneOf(AssetType.class);
|
|
||||||
typeSet.addAll(Arrays.asList(types));
|
|
||||||
return assetRepository
|
|
||||||
.findByBroadcaster(normalize(broadcaster))
|
.findByBroadcaster(normalize(broadcaster))
|
||||||
.stream()
|
.stream()
|
||||||
.filter((asset) -> typeSet.contains(asset.getAssetType()))
|
.filter((asset) -> Arrays.asList(types).contains(asset.getAssetType()))
|
||||||
.map(Asset::getId)
|
.map(Asset::getDisplayOrder)
|
||||||
.toList();
|
.filter(Objects::nonNull)
|
||||||
|
.mapToInt(Integer::intValue)
|
||||||
|
.max()
|
||||||
|
.orElse(0) +
|
||||||
|
1
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private int displayOrderValue(Asset asset) {
|
||||||
|
Integer value = asset == null ? null : asset.getDisplayOrder();
|
||||||
|
return value == null ? Integer.MIN_VALUE : value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<Asset> updateDisplayOrder(String broadcaster, Asset target, int desiredOrder, Set<AssetType> types) {
|
||||||
|
if (target == null || types == null || types.isEmpty()) {
|
||||||
|
return List.of();
|
||||||
|
}
|
||||||
|
List<Asset> ordered = assetRepository
|
||||||
|
.findByBroadcaster(normalize(broadcaster))
|
||||||
|
.stream()
|
||||||
|
.filter((asset) -> types.contains(asset.getAssetType()))
|
||||||
|
.sorted(
|
||||||
|
Comparator.comparingInt((Asset asset) -> displayOrderValue(asset))
|
||||||
|
.reversed()
|
||||||
|
.thenComparing(Asset::getCreatedAt, Comparator.nullsFirst(Comparator.naturalOrder()))
|
||||||
|
)
|
||||||
|
.collect(Collectors.toCollection(ArrayList::new));
|
||||||
|
|
||||||
|
ordered.removeIf((asset) -> asset.getId().equals(target.getId()));
|
||||||
|
int insertIndex = Math.max(0, Math.min(ordered.size(), ordered.size() - desiredOrder));
|
||||||
|
ordered.add(insertIndex, target);
|
||||||
|
|
||||||
|
int size = ordered.size();
|
||||||
|
List<Asset> changed = new ArrayList<>();
|
||||||
|
for (int index = 0; index < size; index++) {
|
||||||
|
Asset asset = ordered.get(index);
|
||||||
|
int nextOrder = size - index;
|
||||||
|
if (asset.getDisplayOrder() == null || asset.getDisplayOrder() != nextOrder) {
|
||||||
|
asset.setDisplayOrder(nextOrder);
|
||||||
|
changed.add(asset);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!changed.isEmpty()) {
|
||||||
|
assetRepository.saveAll(changed);
|
||||||
|
}
|
||||||
|
return changed;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void publishOrderUpdates(String broadcaster, String targetAssetId, List<Asset> updates) {
|
||||||
|
if (updates == null || updates.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
updates
|
||||||
|
.stream()
|
||||||
|
.filter((asset) -> asset.getDisplayOrder() != null)
|
||||||
|
.filter((asset) -> !asset.getId().equals(targetAssetId))
|
||||||
|
.forEach((asset) -> {
|
||||||
|
AssetPatch patch = new AssetPatch(
|
||||||
|
asset.getId(),
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
asset.getDisplayOrder(),
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null
|
||||||
|
);
|
||||||
|
messagingTemplate.convertAndSend(topicFor(broadcaster), AssetEvent.updated(broadcaster, patch));
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private AssetView resolveAssetView(String broadcaster, Asset asset) {
|
private AssetView resolveAssetView(String broadcaster, Asset asset) {
|
||||||
@@ -1818,7 +1891,7 @@ public class ChannelDirectoryService {
|
|||||||
if (req.getWidth() != null) parts.add("width=" + req.getWidth());
|
if (req.getWidth() != null) parts.add("width=" + req.getWidth());
|
||||||
if (req.getHeight() != null) parts.add("height=" + req.getHeight());
|
if (req.getHeight() != null) parts.add("height=" + req.getHeight());
|
||||||
if (req.getRotation() != null) parts.add("rotation=" + req.getRotation());
|
if (req.getRotation() != null) parts.add("rotation=" + req.getRotation());
|
||||||
if (req.getZIndex() != null) parts.add("zIndex=" + req.getZIndex());
|
if (req.getOrder() != null) parts.add("order=" + req.getOrder());
|
||||||
if (req.getSpeed() != null) parts.add("speed=" + req.getSpeed());
|
if (req.getSpeed() != null) parts.add("speed=" + req.getSpeed());
|
||||||
if (req.getMuted() != null) parts.add("muted=" + req.getMuted());
|
if (req.getMuted() != null) parts.add("muted=" + req.getMuted());
|
||||||
if (req.getAudioVolume() != null) parts.add("audioVolume=" + req.getAudioVolume());
|
if (req.getAudioVolume() != null) parts.add("audioVolume=" + req.getAudioVolume());
|
||||||
@@ -1835,10 +1908,10 @@ public class ChannelDirectoryService {
|
|||||||
return formatTransformDetails("Updated audio asset " + assetId, parts);
|
return formatTransformDetails("Updated audio asset " + assetId, parts);
|
||||||
}
|
}
|
||||||
|
|
||||||
private String formatScriptTransformDetails(String assetId, Integer zIndex) {
|
private String formatScriptTransformDetails(String assetId, Integer order) {
|
||||||
String detail = "Updated script asset " + assetId;
|
String detail = "Updated script asset " + assetId;
|
||||||
if (zIndex != null) {
|
if (order != null) {
|
||||||
return detail + " (zIndex=" + zIndex + ")";
|
return detail + " (order=" + order + ")";
|
||||||
}
|
}
|
||||||
return detail;
|
return detail;
|
||||||
}
|
}
|
||||||
@@ -1859,7 +1932,7 @@ public class ChannelDirectoryService {
|
|||||||
patch.rotation() != null ||
|
patch.rotation() != null ||
|
||||||
patch.speed() != null ||
|
patch.speed() != null ||
|
||||||
patch.muted() != null ||
|
patch.muted() != null ||
|
||||||
patch.zIndex() != null ||
|
patch.order() != null ||
|
||||||
patch.hidden() != null ||
|
patch.hidden() != null ||
|
||||||
patch.audioLoop() != null ||
|
patch.audioLoop() != null ||
|
||||||
patch.audioDelayMillis() != null ||
|
patch.audioDelayMillis() != null ||
|
||||||
|
|||||||
20
src/main/resources/db/migration/V8__asset_display_order.sql
Normal file
20
src/main/resources/db/migration/V8__asset_display_order.sql
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
ALTER TABLE assets ADD COLUMN display_order INTEGER;
|
||||||
|
|
||||||
|
UPDATE assets
|
||||||
|
SET display_order = visual_assets.z_index
|
||||||
|
FROM visual_assets
|
||||||
|
WHERE assets.id = visual_assets.id
|
||||||
|
AND assets.asset_type IN ('IMAGE', 'VIDEO', 'MODEL', 'OTHER');
|
||||||
|
|
||||||
|
UPDATE assets
|
||||||
|
SET display_order = script_assets.z_index
|
||||||
|
FROM script_assets
|
||||||
|
WHERE assets.id = script_assets.id
|
||||||
|
AND assets.asset_type = 'SCRIPT';
|
||||||
|
|
||||||
|
UPDATE assets
|
||||||
|
SET display_order = 1
|
||||||
|
WHERE display_order IS NULL;
|
||||||
|
|
||||||
|
ALTER TABLE visual_assets DROP COLUMN z_index;
|
||||||
|
ALTER TABLE script_assets DROP COLUMN z_index;
|
||||||
@@ -47,7 +47,7 @@ export function createAdminConsole({
|
|||||||
const speedLabel = document.getElementById("asset-speed-label");
|
const speedLabel = document.getElementById("asset-speed-label");
|
||||||
const volumeInput = document.getElementById("asset-volume");
|
const volumeInput = document.getElementById("asset-volume");
|
||||||
const volumeLabel = document.getElementById("asset-volume-label");
|
const volumeLabel = document.getElementById("asset-volume-label");
|
||||||
const selectedZLabel = document.getElementById("asset-z-level");
|
const selectedOrderLabel = document.getElementById("asset-order-position");
|
||||||
const playbackSection = document.getElementById("playback-section");
|
const playbackSection = document.getElementById("playback-section");
|
||||||
const volumeSection = document.getElementById("volume-section");
|
const volumeSection = document.getElementById("volume-section");
|
||||||
const audioSection = document.getElementById("audio-section");
|
const audioSection = document.getElementById("audio-section");
|
||||||
@@ -238,6 +238,20 @@ export function createAdminConsole({
|
|||||||
return order.length - index;
|
return order.length - index;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getLayerPosition(assetId) {
|
||||||
|
const order = getLayerOrder();
|
||||||
|
const index = order.indexOf(assetId);
|
||||||
|
if (index === -1) return 1;
|
||||||
|
return index + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getScriptLayerPosition(assetId) {
|
||||||
|
const order = getScriptLayerOrder();
|
||||||
|
const index = order.indexOf(assetId);
|
||||||
|
if (index === -1) return 1;
|
||||||
|
return index + 1;
|
||||||
|
}
|
||||||
|
|
||||||
function addPendingUpload(name) {
|
function addPendingUpload(name) {
|
||||||
const pending = {
|
const pending = {
|
||||||
id: `pending-${Date.now()}-${Math.round(Math.random() * 100000)}`,
|
id: `pending-${Date.now()}-${Math.round(Math.random() * 100000)}`,
|
||||||
@@ -662,23 +676,16 @@ export function createAdminConsole({
|
|||||||
clearMedia(assetId);
|
clearMedia(assetId);
|
||||||
loopPlaybackState.delete(assetId);
|
loopPlaybackState.delete(assetId);
|
||||||
}
|
}
|
||||||
let targetLayer;
|
const targetOrder = Number.isFinite(patch.order) ? patch.order : null;
|
||||||
if (Number.isFinite(patch.layer)) {
|
if (!isAudio && Number.isFinite(targetOrder)) {
|
||||||
targetLayer = patch.layer;
|
|
||||||
} else if (Number.isFinite(patch.zIndex)) {
|
|
||||||
targetLayer = patch.zIndex;
|
|
||||||
} else {
|
|
||||||
targetLayer = null;
|
|
||||||
}
|
|
||||||
if (!isAudio && Number.isFinite(targetLayer)) {
|
|
||||||
if (isScript) {
|
if (isScript) {
|
||||||
const currentOrder = getScriptLayerOrder().filter((id) => id !== assetId);
|
const currentOrder = getScriptLayerOrder().filter((id) => id !== assetId);
|
||||||
const insertIndex = Math.max(0, currentOrder.length - Math.round(targetLayer));
|
const insertIndex = Math.max(0, currentOrder.length - Math.round(targetOrder));
|
||||||
currentOrder.splice(insertIndex, 0, assetId);
|
currentOrder.splice(insertIndex, 0, assetId);
|
||||||
scriptLayerOrder = currentOrder;
|
scriptLayerOrder = currentOrder;
|
||||||
} else {
|
} else {
|
||||||
const currentOrder = getLayerOrder().filter((id) => id !== assetId);
|
const currentOrder = getLayerOrder().filter((id) => id !== assetId);
|
||||||
const insertIndex = Math.max(0, currentOrder.length - Math.round(targetLayer));
|
const insertIndex = Math.max(0, currentOrder.length - Math.round(targetOrder));
|
||||||
currentOrder.splice(insertIndex, 0, assetId);
|
currentOrder.splice(insertIndex, 0, assetId);
|
||||||
layerOrder = currentOrder;
|
layerOrder = currentOrder;
|
||||||
}
|
}
|
||||||
@@ -1340,9 +1347,9 @@ export function createAdminConsole({
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
if (isCodeAsset(asset)) {
|
if (isCodeAsset(asset)) {
|
||||||
return `Script layer ${getScriptLayerValue(asset.id)}`;
|
return `Script order ${getScriptLayerPosition(asset.id)} (above canvas assets)`;
|
||||||
}
|
}
|
||||||
return `Layer ${getLayerValue(asset.id)}`;
|
return `Order ${getLayerPosition(asset.id)}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function createSectionHeader(title) {
|
function createSectionHeader(title) {
|
||||||
@@ -1392,11 +1399,16 @@ export function createAdminConsole({
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!isAudioAsset(asset)) {
|
if (!isAudioAsset(asset)) {
|
||||||
|
const ordered = getLayeredAssets(asset);
|
||||||
|
const orderIndex = ordered.findIndex((item) => item.id === asset.id);
|
||||||
|
const canMoveUp = orderIndex > 0;
|
||||||
|
const canMoveDown = orderIndex !== -1 && orderIndex < ordered.length - 1;
|
||||||
const moveUp = document.createElement("button");
|
const moveUp = document.createElement("button");
|
||||||
moveUp.type = "button";
|
moveUp.type = "button";
|
||||||
moveUp.className = "ghost icon-button";
|
moveUp.className = "ghost icon-button";
|
||||||
moveUp.innerHTML = '<i class="fa-solid fa-arrow-up"></i>';
|
moveUp.innerHTML = '<i class="fa-solid fa-arrow-up"></i>';
|
||||||
moveUp.title = isCodeAsset(asset) ? "Move script up" : "Move layer up";
|
moveUp.title = isCodeAsset(asset) ? "Move script up" : "Move asset up";
|
||||||
|
moveUp.disabled = !canMoveUp;
|
||||||
moveUp.addEventListener("click", (e) => {
|
moveUp.addEventListener("click", (e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
moveLayerItem(asset, "up");
|
moveLayerItem(asset, "up");
|
||||||
@@ -1405,7 +1417,8 @@ export function createAdminConsole({
|
|||||||
moveDown.type = "button";
|
moveDown.type = "button";
|
||||||
moveDown.className = "ghost icon-button";
|
moveDown.className = "ghost icon-button";
|
||||||
moveDown.innerHTML = '<i class="fa-solid fa-arrow-down"></i>';
|
moveDown.innerHTML = '<i class="fa-solid fa-arrow-down"></i>';
|
||||||
moveDown.title = isCodeAsset(asset) ? "Move script down" : "Move layer down";
|
moveDown.title = isCodeAsset(asset) ? "Move script down" : "Move asset down";
|
||||||
|
moveDown.disabled = !canMoveDown;
|
||||||
moveDown.addEventListener("click", (e) => {
|
moveDown.addEventListener("click", (e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
moveLayerItem(asset, "down");
|
moveLayerItem(asset, "down");
|
||||||
@@ -1802,8 +1815,10 @@ export function createAdminConsole({
|
|||||||
|
|
||||||
controlsPanel.classList.remove("hidden");
|
controlsPanel.classList.remove("hidden");
|
||||||
lastSizeInputChanged = null;
|
lastSizeInputChanged = null;
|
||||||
if (selectedZLabel) {
|
if (selectedOrderLabel) {
|
||||||
selectedZLabel.textContent = getLayerValue(asset.id);
|
selectedOrderLabel.textContent = isCodeAsset(asset)
|
||||||
|
? getScriptLayerPosition(asset.id)
|
||||||
|
: getLayerPosition(asset.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (widthInput) widthInput.value = Math.round(asset.width);
|
if (widthInput) widthInput.value = Math.round(asset.width);
|
||||||
@@ -1921,9 +1936,12 @@ export function createAdminConsole({
|
|||||||
if (asset) {
|
if (asset) {
|
||||||
selectedAssetBadges.appendChild(createBadge(getDisplayMediaType(asset)));
|
selectedAssetBadges.appendChild(createBadge(getDisplayMediaType(asset)));
|
||||||
if (isCodeAsset(asset)) {
|
if (isCodeAsset(asset)) {
|
||||||
selectedAssetBadges.appendChild(createBadge(`Script layer ${getScriptLayerValue(asset.id)}`, "subtle"));
|
selectedAssetBadges.appendChild(
|
||||||
|
createBadge(`Script order ${getScriptLayerPosition(asset.id)}`, "subtle"),
|
||||||
|
);
|
||||||
|
selectedAssetBadges.appendChild(createBadge("Above canvas assets", "subtle"));
|
||||||
} else if (!isAudioAsset(asset)) {
|
} else if (!isAudioAsset(asset)) {
|
||||||
selectedAssetBadges.appendChild(createBadge(`Layer ${getLayerValue(asset.id)}`, "subtle"));
|
selectedAssetBadges.appendChild(createBadge(`Order ${getLayerPosition(asset.id)}`, "subtle"));
|
||||||
}
|
}
|
||||||
const aspectLabel = !isAudioAsset(asset) && !isCodeAsset(asset) ? formatAspectRatioLabel(asset) : "";
|
const aspectLabel = !isAudioAsset(asset) && !isCodeAsset(asset) ? formatAspectRatioLabel(asset) : "";
|
||||||
if (aspectLabel) {
|
if (aspectLabel) {
|
||||||
@@ -2445,18 +2463,17 @@ export function createAdminConsole({
|
|||||||
audioVolume: asset.audioVolume,
|
audioVolume: asset.audioVolume,
|
||||||
};
|
};
|
||||||
if (isCodeAsset(asset)) {
|
if (isCodeAsset(asset)) {
|
||||||
const layer = getScriptLayerValue(asset.id);
|
const order = getScriptLayerValue(asset.id);
|
||||||
payload.zIndex = layer;
|
payload.order = order;
|
||||||
} else if (!isAudioAsset(asset)) {
|
} else if (!isAudioAsset(asset)) {
|
||||||
const layer = getLayerValue(asset.id);
|
const order = getLayerValue(asset.id);
|
||||||
payload.x = asset.x;
|
payload.x = asset.x;
|
||||||
payload.y = asset.y;
|
payload.y = asset.y;
|
||||||
payload.width = asset.width;
|
payload.width = asset.width;
|
||||||
payload.height = asset.height;
|
payload.height = asset.height;
|
||||||
payload.rotation = asset.rotation;
|
payload.rotation = asset.rotation;
|
||||||
payload.speed = asset.speed;
|
payload.speed = asset.speed;
|
||||||
payload.layer = layer;
|
payload.order = order;
|
||||||
payload.zIndex = layer;
|
|
||||||
if (isVideoAsset(asset)) {
|
if (isVideoAsset(asset)) {
|
||||||
payload.muted = asset.muted;
|
payload.muted = asset.muted;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -291,21 +291,17 @@ export class BroadcastRenderer {
|
|||||||
this.hideAssetWithTransition(merged);
|
this.hideAssetWithTransition(merged);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const targetLayer = Number.isFinite(patch.layer)
|
const targetOrder = Number.isFinite(patch.order) ? patch.order : null;
|
||||||
? patch.layer
|
if (Number.isFinite(targetOrder)) {
|
||||||
: Number.isFinite(patch.zIndex)
|
|
||||||
? patch.zIndex
|
|
||||||
: null;
|
|
||||||
if (Number.isFinite(targetLayer)) {
|
|
||||||
if (isScript) {
|
if (isScript) {
|
||||||
const currentOrder = getScriptLayerOrder(this.state).filter((id) => id !== assetId);
|
const currentOrder = getScriptLayerOrder(this.state).filter((id) => id !== assetId);
|
||||||
const insertIndex = Math.max(0, currentOrder.length - Math.round(targetLayer));
|
const insertIndex = Math.max(0, currentOrder.length - Math.round(targetOrder));
|
||||||
currentOrder.splice(insertIndex, 0, assetId);
|
currentOrder.splice(insertIndex, 0, assetId);
|
||||||
this.state.scriptLayerOrder = currentOrder;
|
this.state.scriptLayerOrder = currentOrder;
|
||||||
this.applyScriptCanvasOrder();
|
this.applyScriptCanvasOrder();
|
||||||
} else if (isVisual) {
|
} else if (isVisual) {
|
||||||
const currentOrder = getLayerOrder(this.state).filter((id) => id !== assetId);
|
const currentOrder = getLayerOrder(this.state).filter((id) => id !== assetId);
|
||||||
const insertIndex = Math.max(0, currentOrder.length - Math.round(targetLayer));
|
const insertIndex = Math.max(0, currentOrder.length - Math.round(targetOrder));
|
||||||
currentOrder.splice(insertIndex, 0, assetId);
|
currentOrder.splice(insertIndex, 0, assetId);
|
||||||
this.state.layerOrder = currentOrder;
|
this.state.layerOrder = currentOrder;
|
||||||
}
|
}
|
||||||
@@ -827,12 +823,14 @@ export class BroadcastRenderer {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const ordered = getScriptLayerOrder(this.state);
|
const ordered = getScriptLayerOrder(this.state);
|
||||||
ordered.forEach((id, index) => {
|
ordered
|
||||||
|
.slice()
|
||||||
|
.reverse()
|
||||||
|
.forEach((id) => {
|
||||||
const canvas = this.scriptCanvases.get(id);
|
const canvas = this.scriptCanvases.get(id);
|
||||||
if (!canvas) {
|
if (!canvas) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
canvas.style.zIndex = `${ordered.length - index}`;
|
|
||||||
this.scriptLayer.appendChild(canvas);
|
this.scriptLayer.appendChild(canvas);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -100,6 +100,10 @@
|
|||||||
<div class="panel-section" id="layout-section">
|
<div class="panel-section" id="layout-section">
|
||||||
<div class="section-header">
|
<div class="section-header">
|
||||||
<h5>Layout & order</h5>
|
<h5>Layout & order</h5>
|
||||||
|
<p class="subtle-text">
|
||||||
|
The top of the list renders on top in the broadcast. Script assets always render
|
||||||
|
above canvas assets.
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="property-list">
|
<div class="property-list">
|
||||||
<div class="property-row">
|
<div class="property-row">
|
||||||
@@ -132,11 +136,11 @@
|
|||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="property-row">
|
<div class="property-row">
|
||||||
<span class="property-label">Layer</span>
|
<span class="property-label">Order</span>
|
||||||
<div class="property-control">
|
<div class="property-control">
|
||||||
<div class="badge-row stacked">
|
<div class="badge-row stacked">
|
||||||
<span class="badge"
|
<span class="badge"
|
||||||
>Layer <strong id="asset-z-level">1</strong></span
|
>Position <strong id="asset-order-position">1</strong></span
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -198,13 +198,13 @@ class ChannelDirectoryServiceTest {
|
|||||||
TransformRequest transform = validTransform();
|
TransformRequest transform = validTransform();
|
||||||
transform.setSpeed(0.1);
|
transform.setSpeed(0.1);
|
||||||
transform.setAudioVolume(0.01);
|
transform.setAudioVolume(0.01);
|
||||||
transform.setZIndex(1);
|
transform.setOrder(1);
|
||||||
|
|
||||||
AssetView view = service.updateTransform(channel, id, transform, "caster").orElseThrow();
|
AssetView view = service.updateTransform(channel, id, transform, "caster").orElseThrow();
|
||||||
|
|
||||||
assertThat(view.speed()).isEqualTo(0.1);
|
assertThat(view.speed()).isEqualTo(0.1);
|
||||||
assertThat(view.audioVolume()).isEqualTo(0.01);
|
assertThat(view.audioVolume()).isEqualTo(0.01);
|
||||||
assertThat(view.zIndex()).isEqualTo(1);
|
assertThat(assetRepository.findById(id).orElseThrow().getDisplayOrder()).isEqualTo(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
Reference in New Issue
Block a user