mirror of
https://github.com/imgfloat/server.git
synced 2026-02-05 03:39:26 +00:00
Add translation controls
This commit is contained in:
@@ -5,8 +5,22 @@ canvas.width = canvas.offsetWidth;
|
||||
canvas.height = canvas.offsetHeight;
|
||||
const assets = new Map();
|
||||
const imageCache = new Map();
|
||||
const renderStates = new Map();
|
||||
let selectedAssetId = null;
|
||||
let dragState = null;
|
||||
let animationFrameId = null;
|
||||
|
||||
const controlsPanel = document.getElementById('asset-controls');
|
||||
const widthInput = document.getElementById('asset-width');
|
||||
const heightInput = document.getElementById('asset-height');
|
||||
const rotationInput = document.getElementById('asset-rotation');
|
||||
const rotationDisplay = document.getElementById('rotation-display');
|
||||
const selectedAssetName = document.getElementById('selected-asset-name');
|
||||
const selectedAssetMeta = document.getElementById('selected-asset-meta');
|
||||
|
||||
if (rotationInput) {
|
||||
rotationInput.addEventListener('input', updateRotationDisplay);
|
||||
}
|
||||
|
||||
function connect() {
|
||||
const socket = new SockJS('/ws');
|
||||
@@ -33,6 +47,7 @@ function handleEvent(event) {
|
||||
if (event.type === 'DELETED') {
|
||||
assets.delete(event.assetId);
|
||||
imageCache.delete(event.assetId);
|
||||
renderStates.delete(event.assetId);
|
||||
if (selectedAssetId === event.assetId) {
|
||||
selectedAssetId = null;
|
||||
}
|
||||
@@ -54,33 +69,68 @@ function draw() {
|
||||
}
|
||||
|
||||
function drawAsset(asset) {
|
||||
const renderState = smoothState(asset);
|
||||
ctx.save();
|
||||
ctx.translate(asset.x, asset.y);
|
||||
ctx.rotate(asset.rotation * Math.PI / 180);
|
||||
ctx.translate(renderState.x, renderState.y);
|
||||
ctx.rotate(renderState.rotation * Math.PI / 180);
|
||||
|
||||
const image = ensureImage(asset);
|
||||
if (image?.complete) {
|
||||
ctx.globalAlpha = asset.hidden ? 0.35 : 0.9;
|
||||
ctx.drawImage(image, 0, 0, asset.width, asset.height);
|
||||
ctx.drawImage(image, 0, 0, renderState.width, renderState.height);
|
||||
} else {
|
||||
ctx.globalAlpha = asset.hidden ? 0.2 : 0.4;
|
||||
ctx.fillStyle = 'rgba(124, 58, 237, 0.35)';
|
||||
ctx.fillRect(0, 0, asset.width, asset.height);
|
||||
ctx.fillRect(0, 0, renderState.width, renderState.height);
|
||||
}
|
||||
|
||||
if (asset.hidden) {
|
||||
ctx.fillStyle = 'rgba(15, 23, 42, 0.35)';
|
||||
ctx.fillRect(0, 0, asset.width, asset.height);
|
||||
ctx.fillRect(0, 0, renderState.width, renderState.height);
|
||||
}
|
||||
|
||||
ctx.globalAlpha = 1;
|
||||
ctx.strokeStyle = asset.id === selectedAssetId ? 'rgba(124, 58, 237, 0.9)' : 'rgba(255, 255, 255, 0.4)';
|
||||
ctx.lineWidth = asset.id === selectedAssetId ? 2 : 1;
|
||||
ctx.setLineDash(asset.id === selectedAssetId ? [6, 4] : []);
|
||||
ctx.strokeRect(0, 0, asset.width, asset.height);
|
||||
ctx.strokeRect(0, 0, renderState.width, renderState.height);
|
||||
ctx.restore();
|
||||
}
|
||||
|
||||
function smoothState(asset) {
|
||||
const previous = renderStates.get(asset.id) || { ...asset };
|
||||
const factor = dragState && dragState.assetId === asset.id ? 0.5 : 0.18;
|
||||
const next = {
|
||||
x: lerp(previous.x, asset.x, factor),
|
||||
y: lerp(previous.y, asset.y, factor),
|
||||
width: lerp(previous.width, asset.width, factor),
|
||||
height: lerp(previous.height, asset.height, factor),
|
||||
rotation: smoothAngle(previous.rotation, asset.rotation, factor)
|
||||
};
|
||||
renderStates.set(asset.id, next);
|
||||
return next;
|
||||
}
|
||||
|
||||
function smoothAngle(current, target, factor) {
|
||||
let delta = ((target - current + 180) % 360) - 180;
|
||||
return current + delta * factor;
|
||||
}
|
||||
|
||||
function lerp(a, b, t) {
|
||||
return a + (b - a) * t;
|
||||
}
|
||||
|
||||
function startRenderLoop() {
|
||||
if (animationFrameId) {
|
||||
return;
|
||||
}
|
||||
const tick = () => {
|
||||
draw();
|
||||
animationFrameId = requestAnimationFrame(tick);
|
||||
};
|
||||
animationFrameId = requestAnimationFrame(tick);
|
||||
}
|
||||
|
||||
function ensureImage(asset) {
|
||||
const cached = imageCache.get(asset.id);
|
||||
if (cached && cached.src === asset.url) {
|
||||
@@ -102,6 +152,7 @@ function renderAssetList() {
|
||||
const empty = document.createElement('li');
|
||||
empty.textContent = 'No assets yet. Upload to get started.';
|
||||
list.appendChild(empty);
|
||||
updateSelectedAssetControls();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -118,10 +169,15 @@ function renderAssetList() {
|
||||
li.classList.add('hidden');
|
||||
}
|
||||
|
||||
const preview = document.createElement('img');
|
||||
preview.className = 'asset-preview';
|
||||
preview.src = asset.url;
|
||||
preview.alt = asset.name || 'Asset preview';
|
||||
|
||||
const meta = document.createElement('div');
|
||||
meta.className = 'meta';
|
||||
const name = document.createElement('strong');
|
||||
name.textContent = `Asset ${asset.id.slice(0, 6)}`;
|
||||
name.textContent = asset.name || `Asset ${asset.id.slice(0, 6)}`;
|
||||
const details = document.createElement('small');
|
||||
details.textContent = `${Math.round(asset.width)}x${Math.round(asset.height)} · ${asset.hidden ? 'Hidden' : 'Visible'}`;
|
||||
meta.appendChild(name);
|
||||
@@ -154,13 +210,76 @@ function renderAssetList() {
|
||||
|
||||
li.addEventListener('click', () => {
|
||||
selectedAssetId = asset.id;
|
||||
renderStates.set(asset.id, { ...asset });
|
||||
drawAndList();
|
||||
});
|
||||
|
||||
li.appendChild(preview);
|
||||
li.appendChild(meta);
|
||||
li.appendChild(actions);
|
||||
list.appendChild(li);
|
||||
});
|
||||
|
||||
updateSelectedAssetControls();
|
||||
}
|
||||
|
||||
function getSelectedAsset() {
|
||||
return selectedAssetId ? assets.get(selectedAssetId) : null;
|
||||
}
|
||||
|
||||
function updateSelectedAssetControls() {
|
||||
if (!controlsPanel) {
|
||||
return;
|
||||
}
|
||||
const asset = getSelectedAsset();
|
||||
if (!asset) {
|
||||
controlsPanel.classList.add('hidden');
|
||||
return;
|
||||
}
|
||||
|
||||
controlsPanel.classList.remove('hidden');
|
||||
selectedAssetName.textContent = asset.name || `Asset ${asset.id.slice(0, 6)}`;
|
||||
selectedAssetMeta.textContent = `${Math.round(asset.width)}x${Math.round(asset.height)} · ${asset.hidden ? 'Hidden' : 'Visible'}`;
|
||||
|
||||
if (widthInput) widthInput.value = Math.round(asset.width);
|
||||
if (heightInput) heightInput.value = Math.round(asset.height);
|
||||
if (rotationInput) {
|
||||
rotationInput.value = Math.round(asset.rotation);
|
||||
updateRotationDisplay();
|
||||
}
|
||||
}
|
||||
|
||||
function applyTransformFromInputs() {
|
||||
const asset = getSelectedAsset();
|
||||
if (!asset) return;
|
||||
const nextWidth = parseFloat(widthInput?.value) || asset.width;
|
||||
const nextHeight = parseFloat(heightInput?.value) || asset.height;
|
||||
const nextRotation = parseFloat(rotationInput?.value) || 0;
|
||||
|
||||
asset.width = Math.max(10, nextWidth);
|
||||
asset.height = Math.max(10, nextHeight);
|
||||
asset.rotation = nextRotation;
|
||||
renderStates.set(asset.id, { ...asset });
|
||||
persistTransform(asset);
|
||||
drawAndList();
|
||||
}
|
||||
|
||||
function nudgeRotation(delta) {
|
||||
const asset = getSelectedAsset();
|
||||
if (!asset) return;
|
||||
const next = (asset.rotation || 0) + delta;
|
||||
if (rotationInput) rotationInput.value = next;
|
||||
asset.rotation = next;
|
||||
renderStates.set(asset.id, { ...asset });
|
||||
updateRotationDisplay();
|
||||
persistTransform(asset);
|
||||
}
|
||||
|
||||
function updateRotationDisplay() {
|
||||
if (rotationDisplay && rotationInput) {
|
||||
const value = Math.round(parseFloat(rotationInput.value || '0'));
|
||||
rotationDisplay.textContent = `${value}°`;
|
||||
}
|
||||
}
|
||||
|
||||
function updateVisibility(asset, hidden) {
|
||||
@@ -178,6 +297,7 @@ function deleteAsset(asset) {
|
||||
fetch(`/api/channels/${broadcaster}/assets/${asset.id}`, { method: 'DELETE' }).then(() => {
|
||||
assets.delete(asset.id);
|
||||
imageCache.delete(asset.id);
|
||||
renderStates.delete(asset.id);
|
||||
if (selectedAssetId === asset.id) {
|
||||
selectedAssetId = null;
|
||||
}
|
||||
@@ -296,4 +416,5 @@ window.addEventListener('resize', () => {
|
||||
draw();
|
||||
});
|
||||
|
||||
startRenderLoop();
|
||||
connect();
|
||||
|
||||
Reference in New Issue
Block a user