mirror of
https://github.com/imgfloat/server.git
synced 2026-02-05 03:39:26 +00:00
Add redir
This commit is contained in:
@@ -1,8 +1,8 @@
|
||||
package com.imgfloat.app.controller;
|
||||
|
||||
import com.imgfloat.app.model.AdminRequest;
|
||||
import com.imgfloat.app.model.ImageLayer;
|
||||
import com.imgfloat.app.model.ImageRequest;
|
||||
import com.imgfloat.app.model.Asset;
|
||||
import com.imgfloat.app.model.AssetRequest;
|
||||
import com.imgfloat.app.model.TransformRequest;
|
||||
import com.imgfloat.app.model.VisibilityRequest;
|
||||
import com.imgfloat.app.service.ChannelDirectoryService;
|
||||
@@ -61,71 +61,71 @@ public class ChannelApiController {
|
||||
return ResponseEntity.ok().body(removed);
|
||||
}
|
||||
|
||||
@GetMapping("/images")
|
||||
public Collection<ImageLayer> listImages(@PathVariable("broadcaster") String broadcaster,
|
||||
OAuth2AuthenticationToken authentication) {
|
||||
@GetMapping("/assets")
|
||||
public Collection<Asset> listAssets(@PathVariable("broadcaster") String broadcaster,
|
||||
OAuth2AuthenticationToken authentication) {
|
||||
String login = TwitchUser.from(authentication).login();
|
||||
if (!channelDirectoryService.isBroadcaster(broadcaster, login)
|
||||
&& !channelDirectoryService.isAdmin(broadcaster, login)) {
|
||||
throw new ResponseStatusException(FORBIDDEN, "Not authorized");
|
||||
}
|
||||
return channelDirectoryService.getImagesForAdmin(broadcaster);
|
||||
return channelDirectoryService.getAssetsForAdmin(broadcaster);
|
||||
}
|
||||
|
||||
@GetMapping("/images/visible")
|
||||
public Collection<ImageLayer> listVisible(@PathVariable("broadcaster") String broadcaster,
|
||||
OAuth2AuthenticationToken authentication) {
|
||||
@GetMapping("/assets/visible")
|
||||
public Collection<Asset> listVisible(@PathVariable("broadcaster") String broadcaster,
|
||||
OAuth2AuthenticationToken authentication) {
|
||||
String login = TwitchUser.from(authentication).login();
|
||||
if (!channelDirectoryService.isBroadcaster(broadcaster, login)) {
|
||||
throw new ResponseStatusException(FORBIDDEN, "Only broadcaster can load public overlay");
|
||||
}
|
||||
return channelDirectoryService.getVisibleImages(broadcaster);
|
||||
return channelDirectoryService.getVisibleAssets(broadcaster);
|
||||
}
|
||||
|
||||
@PostMapping("/images")
|
||||
public ResponseEntity<ImageLayer> createImage(@PathVariable("broadcaster") String broadcaster,
|
||||
@Valid @RequestBody ImageRequest request,
|
||||
OAuth2AuthenticationToken authentication) {
|
||||
@PostMapping("/assets")
|
||||
public ResponseEntity<Asset> createAsset(@PathVariable("broadcaster") String broadcaster,
|
||||
@Valid @RequestBody AssetRequest request,
|
||||
OAuth2AuthenticationToken authentication) {
|
||||
String login = TwitchUser.from(authentication).login();
|
||||
ensureAuthorized(broadcaster, login);
|
||||
return channelDirectoryService.createImage(broadcaster, request)
|
||||
return channelDirectoryService.createAsset(broadcaster, request)
|
||||
.map(ResponseEntity::ok)
|
||||
.orElseThrow(() -> new ResponseStatusException(NOT_FOUND, "Channel not found"));
|
||||
}
|
||||
|
||||
@PutMapping("/images/{imageId}/transform")
|
||||
public ResponseEntity<ImageLayer> transform(@PathVariable("broadcaster") String broadcaster,
|
||||
@PathVariable("imageId") String imageId,
|
||||
@Valid @RequestBody TransformRequest request,
|
||||
OAuth2AuthenticationToken authentication) {
|
||||
@PutMapping("/assets/{assetId}/transform")
|
||||
public ResponseEntity<Asset> transform(@PathVariable("broadcaster") String broadcaster,
|
||||
@PathVariable("assetId") String assetId,
|
||||
@Valid @RequestBody TransformRequest request,
|
||||
OAuth2AuthenticationToken authentication) {
|
||||
String login = TwitchUser.from(authentication).login();
|
||||
ensureAuthorized(broadcaster, login);
|
||||
return channelDirectoryService.updateTransform(broadcaster, imageId, request)
|
||||
return channelDirectoryService.updateTransform(broadcaster, assetId, request)
|
||||
.map(ResponseEntity::ok)
|
||||
.orElseThrow(() -> new ResponseStatusException(NOT_FOUND, "Image not found"));
|
||||
.orElseThrow(() -> new ResponseStatusException(NOT_FOUND, "Asset not found"));
|
||||
}
|
||||
|
||||
@PutMapping("/images/{imageId}/visibility")
|
||||
public ResponseEntity<ImageLayer> visibility(@PathVariable("broadcaster") String broadcaster,
|
||||
@PathVariable("imageId") String imageId,
|
||||
@RequestBody VisibilityRequest request,
|
||||
OAuth2AuthenticationToken authentication) {
|
||||
@PutMapping("/assets/{assetId}/visibility")
|
||||
public ResponseEntity<Asset> visibility(@PathVariable("broadcaster") String broadcaster,
|
||||
@PathVariable("assetId") String assetId,
|
||||
@RequestBody VisibilityRequest request,
|
||||
OAuth2AuthenticationToken authentication) {
|
||||
String login = TwitchUser.from(authentication).login();
|
||||
ensureAuthorized(broadcaster, login);
|
||||
return channelDirectoryService.updateVisibility(broadcaster, imageId, request)
|
||||
return channelDirectoryService.updateVisibility(broadcaster, assetId, request)
|
||||
.map(ResponseEntity::ok)
|
||||
.orElseThrow(() -> new ResponseStatusException(NOT_FOUND, "Image not found"));
|
||||
.orElseThrow(() -> new ResponseStatusException(NOT_FOUND, "Asset not found"));
|
||||
}
|
||||
|
||||
@DeleteMapping("/images/{imageId}")
|
||||
@DeleteMapping("/assets/{assetId}")
|
||||
public ResponseEntity<?> delete(@PathVariable("broadcaster") String broadcaster,
|
||||
@PathVariable("imageId") String imageId,
|
||||
@PathVariable("assetId") String assetId,
|
||||
OAuth2AuthenticationToken authentication) {
|
||||
String login = TwitchUser.from(authentication).login();
|
||||
ensureAuthorized(broadcaster, login);
|
||||
boolean removed = channelDirectoryService.deleteImage(broadcaster, imageId);
|
||||
boolean removed = channelDirectoryService.deleteAsset(broadcaster, assetId);
|
||||
if (!removed) {
|
||||
throw new ResponseStatusException(NOT_FOUND, "Image not found");
|
||||
throw new ResponseStatusException(NOT_FOUND, "Asset not found");
|
||||
}
|
||||
return ResponseEntity.ok().build();
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ package com.imgfloat.app.model;
|
||||
import java.time.Instant;
|
||||
import java.util.UUID;
|
||||
|
||||
public class ImageLayer {
|
||||
public class Asset {
|
||||
private final String id;
|
||||
private String url;
|
||||
private double x;
|
||||
@@ -14,7 +14,7 @@ public class ImageLayer {
|
||||
private boolean hidden;
|
||||
private final Instant createdAt;
|
||||
|
||||
public ImageLayer(String url, double width, double height) {
|
||||
public Asset(String url, double width, double height) {
|
||||
this.id = UUID.randomUUID().toString();
|
||||
this.url = url;
|
||||
this.width = width;
|
||||
66
src/main/java/com/imgfloat/app/model/AssetEvent.java
Normal file
66
src/main/java/com/imgfloat/app/model/AssetEvent.java
Normal file
@@ -0,0 +1,66 @@
|
||||
package com.imgfloat.app.model;
|
||||
|
||||
public class AssetEvent {
|
||||
public enum Type {
|
||||
CREATED,
|
||||
UPDATED,
|
||||
VISIBILITY,
|
||||
DELETED
|
||||
}
|
||||
|
||||
private Type type;
|
||||
private String channel;
|
||||
private Asset payload;
|
||||
private String assetId;
|
||||
|
||||
public static AssetEvent created(String channel, Asset asset) {
|
||||
AssetEvent event = new AssetEvent();
|
||||
event.type = Type.CREATED;
|
||||
event.channel = channel;
|
||||
event.payload = asset;
|
||||
event.assetId = asset.getId();
|
||||
return event;
|
||||
}
|
||||
|
||||
public static AssetEvent updated(String channel, Asset asset) {
|
||||
AssetEvent event = new AssetEvent();
|
||||
event.type = Type.UPDATED;
|
||||
event.channel = channel;
|
||||
event.payload = asset;
|
||||
event.assetId = asset.getId();
|
||||
return event;
|
||||
}
|
||||
|
||||
public static AssetEvent visibility(String channel, Asset asset) {
|
||||
AssetEvent event = new AssetEvent();
|
||||
event.type = Type.VISIBILITY;
|
||||
event.channel = channel;
|
||||
event.payload = asset;
|
||||
event.assetId = asset.getId();
|
||||
return event;
|
||||
}
|
||||
|
||||
public static AssetEvent deleted(String channel, String assetId) {
|
||||
AssetEvent event = new AssetEvent();
|
||||
event.type = Type.DELETED;
|
||||
event.channel = channel;
|
||||
event.assetId = assetId;
|
||||
return event;
|
||||
}
|
||||
|
||||
public Type getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public String getChannel() {
|
||||
return channel;
|
||||
}
|
||||
|
||||
public Asset getPayload() {
|
||||
return payload;
|
||||
}
|
||||
|
||||
public String getAssetId() {
|
||||
return assetId;
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,7 @@ package com.imgfloat.app.model;
|
||||
import jakarta.validation.constraints.Min;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
|
||||
public class ImageRequest {
|
||||
public class AssetRequest {
|
||||
@NotBlank
|
||||
private String url;
|
||||
|
||||
@@ -8,12 +8,12 @@ import java.util.concurrent.ConcurrentHashMap;
|
||||
public class Channel {
|
||||
private final String broadcaster;
|
||||
private final Set<String> admins;
|
||||
private final Map<String, ImageLayer> images;
|
||||
private final Map<String, Asset> assets;
|
||||
|
||||
public Channel(String broadcaster) {
|
||||
this.broadcaster = broadcaster.toLowerCase();
|
||||
this.admins = ConcurrentHashMap.newKeySet();
|
||||
this.images = new ConcurrentHashMap<>();
|
||||
this.assets = new ConcurrentHashMap<>();
|
||||
}
|
||||
|
||||
public String getBroadcaster() {
|
||||
@@ -24,8 +24,8 @@ public class Channel {
|
||||
return Collections.unmodifiableSet(admins);
|
||||
}
|
||||
|
||||
public Map<String, ImageLayer> getImages() {
|
||||
return images;
|
||||
public Map<String, Asset> getAssets() {
|
||||
return assets;
|
||||
}
|
||||
|
||||
public boolean addAdmin(String username) {
|
||||
|
||||
@@ -1,66 +0,0 @@
|
||||
package com.imgfloat.app.model;
|
||||
|
||||
public class ImageEvent {
|
||||
public enum Type {
|
||||
CREATED,
|
||||
UPDATED,
|
||||
VISIBILITY,
|
||||
DELETED
|
||||
}
|
||||
|
||||
private Type type;
|
||||
private String channel;
|
||||
private ImageLayer payload;
|
||||
private String imageId;
|
||||
|
||||
public static ImageEvent created(String channel, ImageLayer layer) {
|
||||
ImageEvent event = new ImageEvent();
|
||||
event.type = Type.CREATED;
|
||||
event.channel = channel;
|
||||
event.payload = layer;
|
||||
event.imageId = layer.getId();
|
||||
return event;
|
||||
}
|
||||
|
||||
public static ImageEvent updated(String channel, ImageLayer layer) {
|
||||
ImageEvent event = new ImageEvent();
|
||||
event.type = Type.UPDATED;
|
||||
event.channel = channel;
|
||||
event.payload = layer;
|
||||
event.imageId = layer.getId();
|
||||
return event;
|
||||
}
|
||||
|
||||
public static ImageEvent visibility(String channel, ImageLayer layer) {
|
||||
ImageEvent event = new ImageEvent();
|
||||
event.type = Type.VISIBILITY;
|
||||
event.channel = channel;
|
||||
event.payload = layer;
|
||||
event.imageId = layer.getId();
|
||||
return event;
|
||||
}
|
||||
|
||||
public static ImageEvent deleted(String channel, String imageId) {
|
||||
ImageEvent event = new ImageEvent();
|
||||
event.type = Type.DELETED;
|
||||
event.channel = channel;
|
||||
event.imageId = imageId;
|
||||
return event;
|
||||
}
|
||||
|
||||
public Type getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public String getChannel() {
|
||||
return channel;
|
||||
}
|
||||
|
||||
public ImageLayer getPayload() {
|
||||
return payload;
|
||||
}
|
||||
|
||||
public String getImageId() {
|
||||
return imageId;
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
package com.imgfloat.app.service;
|
||||
|
||||
import com.imgfloat.app.model.Asset;
|
||||
import com.imgfloat.app.model.AssetEvent;
|
||||
import com.imgfloat.app.model.AssetRequest;
|
||||
import com.imgfloat.app.model.Channel;
|
||||
import com.imgfloat.app.model.ImageEvent;
|
||||
import com.imgfloat.app.model.ImageLayer;
|
||||
import com.imgfloat.app.model.ImageRequest;
|
||||
import com.imgfloat.app.model.TransformRequest;
|
||||
import com.imgfloat.app.model.VisibilityRequest;
|
||||
import org.springframework.messaging.simp.SimpMessagingTemplate;
|
||||
@@ -45,55 +45,55 @@ public class ChannelDirectoryService {
|
||||
return removed;
|
||||
}
|
||||
|
||||
public Collection<ImageLayer> getImagesForAdmin(String broadcaster) {
|
||||
return getOrCreateChannel(broadcaster).getImages().values();
|
||||
public Collection<Asset> getAssetsForAdmin(String broadcaster) {
|
||||
return getOrCreateChannel(broadcaster).getAssets().values();
|
||||
}
|
||||
|
||||
public Collection<ImageLayer> getVisibleImages(String broadcaster) {
|
||||
return getOrCreateChannel(broadcaster).getImages().values().stream()
|
||||
.filter(image -> !image.isHidden())
|
||||
public Collection<Asset> getVisibleAssets(String broadcaster) {
|
||||
return getOrCreateChannel(broadcaster).getAssets().values().stream()
|
||||
.filter(asset -> !asset.isHidden())
|
||||
.toList();
|
||||
}
|
||||
|
||||
public Optional<ImageLayer> createImage(String broadcaster, ImageRequest request) {
|
||||
public Optional<Asset> createAsset(String broadcaster, AssetRequest request) {
|
||||
Channel channel = getOrCreateChannel(broadcaster);
|
||||
ImageLayer layer = new ImageLayer(request.getUrl(), request.getWidth(), request.getHeight());
|
||||
channel.getImages().put(layer.getId(), layer);
|
||||
messagingTemplate.convertAndSend(topicFor(broadcaster), ImageEvent.created(broadcaster, layer));
|
||||
return Optional.of(layer);
|
||||
Asset asset = new Asset(request.getUrl(), request.getWidth(), request.getHeight());
|
||||
channel.getAssets().put(asset.getId(), asset);
|
||||
messagingTemplate.convertAndSend(topicFor(broadcaster), AssetEvent.created(broadcaster, asset));
|
||||
return Optional.of(asset);
|
||||
}
|
||||
|
||||
public Optional<ImageLayer> updateTransform(String broadcaster, String imageId, TransformRequest request) {
|
||||
public Optional<Asset> updateTransform(String broadcaster, String assetId, TransformRequest request) {
|
||||
Channel channel = getOrCreateChannel(broadcaster);
|
||||
ImageLayer layer = channel.getImages().get(imageId);
|
||||
if (layer == null) {
|
||||
Asset asset = channel.getAssets().get(assetId);
|
||||
if (asset == null) {
|
||||
return Optional.empty();
|
||||
}
|
||||
layer.setX(request.getX());
|
||||
layer.setY(request.getY());
|
||||
layer.setWidth(request.getWidth());
|
||||
layer.setHeight(request.getHeight());
|
||||
layer.setRotation(request.getRotation());
|
||||
messagingTemplate.convertAndSend(topicFor(broadcaster), ImageEvent.updated(broadcaster, layer));
|
||||
return Optional.of(layer);
|
||||
asset.setX(request.getX());
|
||||
asset.setY(request.getY());
|
||||
asset.setWidth(request.getWidth());
|
||||
asset.setHeight(request.getHeight());
|
||||
asset.setRotation(request.getRotation());
|
||||
messagingTemplate.convertAndSend(topicFor(broadcaster), AssetEvent.updated(broadcaster, asset));
|
||||
return Optional.of(asset);
|
||||
}
|
||||
|
||||
public Optional<ImageLayer> updateVisibility(String broadcaster, String imageId, VisibilityRequest request) {
|
||||
public Optional<Asset> updateVisibility(String broadcaster, String assetId, VisibilityRequest request) {
|
||||
Channel channel = getOrCreateChannel(broadcaster);
|
||||
ImageLayer layer = channel.getImages().get(imageId);
|
||||
if (layer == null) {
|
||||
Asset asset = channel.getAssets().get(assetId);
|
||||
if (asset == null) {
|
||||
return Optional.empty();
|
||||
}
|
||||
layer.setHidden(request.isHidden());
|
||||
messagingTemplate.convertAndSend(topicFor(broadcaster), ImageEvent.visibility(broadcaster, layer));
|
||||
return Optional.of(layer);
|
||||
asset.setHidden(request.isHidden());
|
||||
messagingTemplate.convertAndSend(topicFor(broadcaster), AssetEvent.visibility(broadcaster, asset));
|
||||
return Optional.of(asset);
|
||||
}
|
||||
|
||||
public boolean deleteImage(String broadcaster, String imageId) {
|
||||
public boolean deleteAsset(String broadcaster, String assetId) {
|
||||
Channel channel = getOrCreateChannel(broadcaster);
|
||||
ImageLayer removed = channel.getImages().remove(imageId);
|
||||
Asset removed = channel.getAssets().remove(assetId);
|
||||
if (removed != null) {
|
||||
messagingTemplate.convertAndSend(topicFor(broadcaster), ImageEvent.deleted(broadcaster, imageId));
|
||||
messagingTemplate.convertAndSend(topicFor(broadcaster), AssetEvent.deleted(broadcaster, assetId));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
||||
@@ -20,7 +20,7 @@ spring:
|
||||
twitch:
|
||||
client-id: ${TWITCH_CLIENT_ID}
|
||||
client-secret: ${TWITCH_CLIENT_SECRET}
|
||||
redirect-uri: "{baseUrl}/login/oauth2/code/twitch"
|
||||
redirect-uri: "${TWITCH_REDIRECT_URI:{baseUrl}/login/oauth2/code/twitch}"
|
||||
authorization-grant-type: authorization_code
|
||||
scope: ["user:read:email"]
|
||||
provider:
|
||||
|
||||
@@ -3,7 +3,7 @@ const canvas = document.getElementById('admin-canvas');
|
||||
const ctx = canvas.getContext('2d');
|
||||
canvas.width = canvas.offsetWidth;
|
||||
canvas.height = canvas.offsetHeight;
|
||||
const images = new Map();
|
||||
const assets = new Map();
|
||||
|
||||
function connect() {
|
||||
const socket = new SockJS('/ws');
|
||||
@@ -13,13 +13,13 @@ function connect() {
|
||||
const body = JSON.parse(payload.body);
|
||||
handleEvent(body);
|
||||
});
|
||||
fetchImages();
|
||||
fetchAssets();
|
||||
fetchAdmins();
|
||||
});
|
||||
}
|
||||
|
||||
function fetchImages() {
|
||||
fetch(`/api/channels/${broadcaster}/images`).then(r => r.json()).then(renderImages);
|
||||
function fetchAssets() {
|
||||
fetch(`/api/channels/${broadcaster}/assets`).then(r => r.json()).then(renderAssets);
|
||||
}
|
||||
|
||||
function fetchAdmins() {
|
||||
@@ -34,38 +34,38 @@ function fetchAdmins() {
|
||||
}).catch(() => {});
|
||||
}
|
||||
|
||||
function renderImages(list) {
|
||||
list.forEach(img => images.set(img.id, img));
|
||||
function renderAssets(list) {
|
||||
list.forEach(asset => assets.set(asset.id, asset));
|
||||
draw();
|
||||
}
|
||||
|
||||
function handleEvent(event) {
|
||||
if (event.type === 'DELETED') {
|
||||
images.delete(event.imageId);
|
||||
assets.delete(event.assetId);
|
||||
} else if (event.payload) {
|
||||
images.set(event.payload.id, event.payload);
|
||||
assets.set(event.payload.id, event.payload);
|
||||
}
|
||||
draw();
|
||||
}
|
||||
|
||||
function draw() {
|
||||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||
images.forEach(img => {
|
||||
assets.forEach(asset => {
|
||||
ctx.save();
|
||||
ctx.globalAlpha = img.hidden ? 0.35 : 1;
|
||||
ctx.translate(img.x, img.y);
|
||||
ctx.rotate(img.rotation * Math.PI / 180);
|
||||
ctx.globalAlpha = asset.hidden ? 0.35 : 1;
|
||||
ctx.translate(asset.x, asset.y);
|
||||
ctx.rotate(asset.rotation * Math.PI / 180);
|
||||
ctx.fillStyle = 'rgba(124, 58, 237, 0.25)';
|
||||
ctx.fillRect(0, 0, img.width, img.height);
|
||||
ctx.fillRect(0, 0, asset.width, asset.height);
|
||||
ctx.restore();
|
||||
});
|
||||
}
|
||||
|
||||
function uploadImage() {
|
||||
const url = document.getElementById('image-url').value;
|
||||
const width = parseFloat(document.getElementById('image-width').value);
|
||||
const height = parseFloat(document.getElementById('image-height').value);
|
||||
fetch(`/api/channels/${broadcaster}/images`, {
|
||||
function uploadAsset() {
|
||||
const url = document.getElementById('asset-url').value;
|
||||
const width = parseFloat(document.getElementById('asset-width').value);
|
||||
const height = parseFloat(document.getElementById('asset-height').value);
|
||||
fetch(`/api/channels/${broadcaster}/assets`, {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({url, width, height})
|
||||
|
||||
@@ -2,7 +2,7 @@ const canvas = document.getElementById('broadcast-canvas');
|
||||
const ctx = canvas.getContext('2d');
|
||||
canvas.width = window.innerWidth;
|
||||
canvas.height = window.innerHeight;
|
||||
const images = new Map();
|
||||
const assets = new Map();
|
||||
|
||||
function connect() {
|
||||
const socket = new SockJS('/ws');
|
||||
@@ -12,37 +12,37 @@ function connect() {
|
||||
const body = JSON.parse(payload.body);
|
||||
handleEvent(body);
|
||||
});
|
||||
fetch(`/api/channels/${broadcaster}/images/visible`).then(r => r.json()).then(renderImages);
|
||||
fetch(`/api/channels/${broadcaster}/assets/visible`).then(r => r.json()).then(renderAssets);
|
||||
});
|
||||
}
|
||||
|
||||
function renderImages(list) {
|
||||
list.forEach(img => images.set(img.id, img));
|
||||
function renderAssets(list) {
|
||||
list.forEach(asset => assets.set(asset.id, asset));
|
||||
draw();
|
||||
}
|
||||
|
||||
function handleEvent(event) {
|
||||
if (event.type === 'DELETED') {
|
||||
images.delete(event.imageId);
|
||||
assets.delete(event.assetId);
|
||||
} else if (event.payload && !event.payload.hidden) {
|
||||
images.set(event.payload.id, event.payload);
|
||||
assets.set(event.payload.id, event.payload);
|
||||
} else if (event.payload && event.payload.hidden) {
|
||||
images.delete(event.payload.id);
|
||||
assets.delete(event.payload.id);
|
||||
}
|
||||
draw();
|
||||
}
|
||||
|
||||
function draw() {
|
||||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||
images.forEach(img => {
|
||||
assets.forEach(asset => {
|
||||
ctx.save();
|
||||
ctx.globalAlpha = 1;
|
||||
ctx.translate(img.x, img.y);
|
||||
ctx.rotate(img.rotation * Math.PI / 180);
|
||||
ctx.translate(asset.x, asset.y);
|
||||
ctx.rotate(asset.rotation * Math.PI / 180);
|
||||
const image = new Image();
|
||||
image.src = img.url;
|
||||
image.src = asset.url;
|
||||
image.onload = () => {
|
||||
ctx.drawImage(image, 0, 0, img.width, img.height);
|
||||
ctx.drawImage(image, 0, 0, asset.width, asset.height);
|
||||
};
|
||||
ctx.restore();
|
||||
});
|
||||
|
||||
@@ -26,12 +26,12 @@
|
||||
<ul id="admin-list"></ul>
|
||||
</div>
|
||||
<div>
|
||||
<h3>Images</h3>
|
||||
<input id="image-url" placeholder="Image URL" />
|
||||
<input id="image-width" placeholder="Width" type="number" value="600" />
|
||||
<input id="image-height" placeholder="Height" type="number" value="400" />
|
||||
<button onclick="uploadImage()">Upload</button>
|
||||
<ul id="image-list"></ul>
|
||||
<h3>Assets</h3>
|
||||
<input id="asset-url" placeholder="Asset URL" />
|
||||
<input id="asset-width" placeholder="Width" type="number" value="600" />
|
||||
<input id="asset-height" placeholder="Height" type="number" value="400" />
|
||||
<button onclick="uploadAsset()">Upload</button>
|
||||
<ul id="asset-list"></ul>
|
||||
</div>
|
||||
</section>
|
||||
<section class="overlay">
|
||||
|
||||
Reference in New Issue
Block a user