feat: Avatar Position-Offset (translate X/Y)
Backend: - AgentRecord + SessionRecord: AvatarOffsetX, AvatarOffsetY Properties - DB-Migration: avatar_offset_x/y REAL DEFAULT 0 auf agents + sessions - Agent/Session Endpoints: Create + Update lesen/schreiben Offset-Felder - GetSessionsWithStats: Offset-Felder ans Frontend Frontend: - Avatar-Editor: X/Y Offset-Slider (-50% bis +50%) im Modal - buildTransformStyle() Helper für transform: scale() translate() - getOffsetX()/getOffsetY() Public API - Reset bei Clear/AI-Generate/Upload - ui.js: buildAgentCard + buildSessionCard rendern transform mit Offset - Agent-Page + Session-Page: initialOffsetX/Y + Save - CSS: .av-offset-row Styles (gruppiert mit .av-scale-row)
This commit is contained in:
1 parent
95b3683910
commit
4775b8ded7
10 files changed
+143
-30
No files matched your search
+85
-11
@@ -6,13 +6,17 @@
|
||||
// previewEl: document.getElementById('myPreview'),
|
||||
// initialImage: 'data:image/...' || null,
|
||||
// initialScale: 1.5,
|
||||
// onChange(imageData, scale) { ... }
|
||||
// initialOffsetX: 0,
|
||||
// initialOffsetY: 0,
|
||||
// onChange(imageData, scale, offsetX, offsetY) { ... }
|
||||
// });
|
||||
// // Cleanup:
|
||||
// editor.destroy();
|
||||
// // Get current values:
|
||||
// editor.getImage(); // data-url or null
|
||||
// editor.getScale(); // number
|
||||
// editor.getOffsetX(); // number (-50 to 50)
|
||||
// editor.getOffsetY(); // number (-50 to 50)
|
||||
|
||||
var AvatarEditor = (function () {
|
||||
|
||||
@@ -20,6 +24,8 @@ var AvatarEditor = (function () {
|
||||
var previewEl = opts.previewEl;
|
||||
var imageData = opts.initialImage || null;
|
||||
var scale = opts.initialScale || 1;
|
||||
var offsetX = opts.initialOffsetX || 0;
|
||||
var offsetY = opts.initialOffsetY || 0;
|
||||
var onChange = opts.onChange || function () {};
|
||||
var overlay = null;
|
||||
var pasteHandler = null;
|
||||
@@ -28,10 +34,17 @@ var AvatarEditor = (function () {
|
||||
// Krea-Status prüfen
|
||||
apiKreaStatus().then(function (s) { kreaAvailable = s.configured; }).catch(function () {});
|
||||
|
||||
function buildTransformStyle() {
|
||||
var parts = [];
|
||||
if (scale !== 1) parts.push('scale(' + scale + ')');
|
||||
if (offsetX !== 0 || offsetY !== 0) parts.push('translate(' + offsetX + '%, ' + offsetY + '%)');
|
||||
return parts.length ? ' style="transform: ' + parts.join(' ') + '"' : '';
|
||||
}
|
||||
|
||||
// Preview rendern
|
||||
function renderPreview() {
|
||||
if (imageData) {
|
||||
previewEl.innerHTML = '<img src="' + imageData + '" alt="" style="scale: ' + scale + '">';
|
||||
previewEl.innerHTML = '<img src="' + imageData + '" alt=""' + buildTransformStyle() + '>';
|
||||
previewEl.classList.add('has-image');
|
||||
} else {
|
||||
previewEl.innerHTML = '?';
|
||||
@@ -65,7 +78,7 @@ var AvatarEditor = (function () {
|
||||
reader.onload = function () {
|
||||
imageData = reader.result;
|
||||
renderPreview();
|
||||
onChange(imageData, scale);
|
||||
onChange(imageData, scale, offsetX, offsetY);
|
||||
if (overlay) refreshModal();
|
||||
};
|
||||
reader.readAsDataURL(file);
|
||||
@@ -74,8 +87,10 @@ var AvatarEditor = (function () {
|
||||
function setImage(data) {
|
||||
imageData = data;
|
||||
scale = 1;
|
||||
offsetX = 0;
|
||||
offsetY = 0;
|
||||
renderPreview();
|
||||
onChange(imageData, scale);
|
||||
onChange(imageData, scale, offsetX, offsetY);
|
||||
}
|
||||
|
||||
function openModal() {
|
||||
@@ -91,7 +106,7 @@ var AvatarEditor = (function () {
|
||||
'</div>' +
|
||||
'<div class="av-body">' +
|
||||
'<div class="av-preview-large">' +
|
||||
(imageData ? '<img src="' + imageData + '" alt="" style="scale: ' + scale + '">' : '?') +
|
||||
(imageData ? '<img src="' + imageData + '" alt=""' + buildTransformStyle() + '>' : '?') +
|
||||
'</div>' +
|
||||
'<div class="av-controls">' +
|
||||
'<div class="av-scale-row"' + (imageData ? '' : ' hidden') + '>' +
|
||||
@@ -99,6 +114,16 @@ var AvatarEditor = (function () {
|
||||
'<input type="range" class="av-scale" min="0.5" max="3" step="0.1" value="' + scale + '">' +
|
||||
'<span class="av-scale-value">' + scale + '×</span>' +
|
||||
'</div>' +
|
||||
'<div class="av-offset-row"' + (imageData ? '' : ' hidden') + '>' +
|
||||
'<label>X</label>' +
|
||||
'<input type="range" class="av-offset-x" min="-50" max="50" step="1" value="' + offsetX + '">' +
|
||||
'<span class="av-offset-x-value">' + offsetX + '%</span>' +
|
||||
'</div>' +
|
||||
'<div class="av-offset-row"' + (imageData ? '' : ' hidden') + '>' +
|
||||
'<label>Y</label>' +
|
||||
'<input type="range" class="av-offset-y" min="-50" max="50" step="1" value="' + offsetY + '">' +
|
||||
'<span class="av-offset-y-value">' + offsetY + '%</span>' +
|
||||
'</div>' +
|
||||
'<div class="av-actions">' +
|
||||
'<button class="av-upload-btn"><i class="fa fa-upload"></i> Hochladen</button>' +
|
||||
'<input type="file" class="av-file-input" accept="image/*" hidden>' +
|
||||
@@ -123,6 +148,11 @@ var AvatarEditor = (function () {
|
||||
var scaleRow = overlay.querySelector('.av-scale-row');
|
||||
var scaleInput = overlay.querySelector('.av-scale');
|
||||
var scaleValueEl = overlay.querySelector('.av-scale-value');
|
||||
var offsetRows = overlay.querySelectorAll('.av-offset-row');
|
||||
var offsetXInput = overlay.querySelector('.av-offset-x');
|
||||
var offsetXValueEl = overlay.querySelector('.av-offset-x-value');
|
||||
var offsetYInput = overlay.querySelector('.av-offset-y');
|
||||
var offsetYValueEl = overlay.querySelector('.av-offset-y-value');
|
||||
var uploadBtn = overlay.querySelector('.av-upload-btn');
|
||||
var fileInput = overlay.querySelector('.av-file-input');
|
||||
var clearBtn = overlay.querySelector('.av-clear-btn');
|
||||
@@ -135,14 +165,41 @@ var AvatarEditor = (function () {
|
||||
if (e.target === overlay) closeModal();
|
||||
});
|
||||
|
||||
function updatePreviewLargeTransform() {
|
||||
var img = previewLarge.querySelector('img');
|
||||
if (img) {
|
||||
var parts = [];
|
||||
if (scale !== 1) parts.push('scale(' + scale + ')');
|
||||
if (offsetX !== 0 || offsetY !== 0) parts.push('translate(' + offsetX + '%, ' + offsetY + '%)');
|
||||
img.style.transform = parts.length ? parts.join(' ') : '';
|
||||
}
|
||||
}
|
||||
|
||||
// Scale
|
||||
scaleInput.addEventListener('input', function () {
|
||||
scale = parseFloat(scaleInput.value);
|
||||
scaleValueEl.textContent = scale + '×';
|
||||
var img = previewLarge.querySelector('img');
|
||||
if (img) img.style.scale = scale;
|
||||
updatePreviewLargeTransform();
|
||||
renderPreview();
|
||||
onChange(imageData, scale);
|
||||
onChange(imageData, scale, offsetX, offsetY);
|
||||
});
|
||||
|
||||
// Offset X
|
||||
offsetXInput.addEventListener('input', function () {
|
||||
offsetX = parseFloat(offsetXInput.value);
|
||||
offsetXValueEl.textContent = offsetX + '%';
|
||||
updatePreviewLargeTransform();
|
||||
renderPreview();
|
||||
onChange(imageData, scale, offsetX, offsetY);
|
||||
});
|
||||
|
||||
// Offset Y
|
||||
offsetYInput.addEventListener('input', function () {
|
||||
offsetY = parseFloat(offsetYInput.value);
|
||||
offsetYValueEl.textContent = offsetY + '%';
|
||||
updatePreviewLargeTransform();
|
||||
renderPreview();
|
||||
onChange(imageData, scale, offsetX, offsetY);
|
||||
});
|
||||
|
||||
// Upload
|
||||
@@ -155,10 +212,16 @@ var AvatarEditor = (function () {
|
||||
clearBtn.addEventListener('click', function () {
|
||||
imageData = null;
|
||||
scale = 1;
|
||||
offsetX = 0;
|
||||
offsetY = 0;
|
||||
scaleInput.value = 1;
|
||||
scaleValueEl.textContent = '1×';
|
||||
offsetXInput.value = 0;
|
||||
offsetXValueEl.textContent = '0%';
|
||||
offsetYInput.value = 0;
|
||||
offsetYValueEl.textContent = '0%';
|
||||
renderPreview();
|
||||
onChange(imageData, scale);
|
||||
onChange(imageData, scale, offsetX, offsetY);
|
||||
refreshModal();
|
||||
});
|
||||
|
||||
@@ -182,10 +245,16 @@ var AvatarEditor = (function () {
|
||||
reader.onload = function () {
|
||||
imageData = reader.result;
|
||||
scale = 1;
|
||||
offsetX = 0;
|
||||
offsetY = 0;
|
||||
scaleInput.value = 1;
|
||||
scaleValueEl.textContent = '1×';
|
||||
offsetXInput.value = 0;
|
||||
offsetXValueEl.textContent = '0%';
|
||||
offsetYInput.value = 0;
|
||||
offsetYValueEl.textContent = '0%';
|
||||
renderPreview();
|
||||
onChange(imageData, scale);
|
||||
onChange(imageData, scale, offsetX, offsetY);
|
||||
refreshModal();
|
||||
};
|
||||
reader.readAsDataURL(blob);
|
||||
@@ -222,14 +291,17 @@ var AvatarEditor = (function () {
|
||||
if (!overlay) return;
|
||||
var previewLarge = overlay.querySelector('.av-preview-large');
|
||||
var scaleRow = overlay.querySelector('.av-scale-row');
|
||||
var offsetRows = overlay.querySelectorAll('.av-offset-row');
|
||||
var clearBtn = overlay.querySelector('.av-clear-btn');
|
||||
if (imageData) {
|
||||
previewLarge.innerHTML = '<img src="' + imageData + '" alt="" style="scale: ' + scale + '">';
|
||||
previewLarge.innerHTML = '<img src="' + imageData + '" alt=""' + buildTransformStyle() + '>';
|
||||
scaleRow.hidden = false;
|
||||
offsetRows.forEach(function (r) { r.hidden = false; });
|
||||
clearBtn.hidden = false;
|
||||
} else {
|
||||
previewLarge.innerHTML = '?';
|
||||
scaleRow.hidden = true;
|
||||
offsetRows.forEach(function (r) { r.hidden = true; });
|
||||
clearBtn.hidden = true;
|
||||
}
|
||||
}
|
||||
@@ -245,6 +317,8 @@ var AvatarEditor = (function () {
|
||||
return {
|
||||
getImage: function () { return imageData; },
|
||||
getScale: function () { return scale; },
|
||||
getOffsetX: function () { return offsetX; },
|
||||
getOffsetY: function () { return offsetY; },
|
||||
destroy: function () {
|
||||
closeModal();
|
||||
if (pasteHandler) {
|
||||
|
||||
@@ -38,7 +38,9 @@ registerPage('agent', {
|
||||
_agentAvatarEditor = AvatarEditor.init({
|
||||
previewEl: avatarPreview,
|
||||
initialImage: (agent && agent.avatar) || null,
|
||||
initialScale: (agent && agent.avatarScale) || 1
|
||||
initialScale: (agent && agent.avatarScale) || 1,
|
||||
initialOffsetX: (agent && agent.avatarOffsetX) || 0,
|
||||
initialOffsetY: (agent && agent.avatarOffsetY) || 0
|
||||
});
|
||||
|
||||
if (agent) {
|
||||
@@ -100,6 +102,8 @@ registerPage('agent', {
|
||||
webSearchVersion: webSearchVersionEl.value,
|
||||
avatar: _agentAvatarEditor.getImage(),
|
||||
avatarScale: _agentAvatarEditor.getScale(),
|
||||
avatarOffsetX: _agentAvatarEditor.getOffsetX(),
|
||||
avatarOffsetY: _agentAvatarEditor.getOffsetY(),
|
||||
customCommands: getCommandsFromUI(commandsList)
|
||||
};
|
||||
|
||||
|
||||
@@ -37,7 +37,9 @@ registerPage('session', {
|
||||
_sessionAvatarEditor = AvatarEditor.init({
|
||||
previewEl: imagePreview,
|
||||
initialImage: (session && session.image) || null,
|
||||
initialScale: (session && session.avatarScale) || 1
|
||||
initialScale: (session && session.avatarScale) || 1,
|
||||
initialOffsetX: (session && session.avatarOffsetX) || 0,
|
||||
initialOffsetY: (session && session.avatarOffsetY) || 0
|
||||
});
|
||||
|
||||
if (session) {
|
||||
@@ -190,7 +192,9 @@ registerPage('session', {
|
||||
context: _sessionContextMde.value().trim() || null,
|
||||
workspace: workspaceEl.value.trim() || null,
|
||||
customCommands: commands,
|
||||
avatarScale: _sessionAvatarEditor.getScale()
|
||||
avatarScale: _sessionAvatarEditor.getScale(),
|
||||
avatarOffsetX: _sessionAvatarEditor.getOffsetX(),
|
||||
avatarOffsetY: _sessionAvatarEditor.getOffsetY()
|
||||
};
|
||||
|
||||
if (isNew) {
|
||||
|
||||
+10
-4
@@ -7,8 +7,11 @@ function buildSessionCard(session, agent) {
|
||||
const card = document.createElement('div');
|
||||
card.className = 'card';
|
||||
|
||||
const scaleStyle = session.avatarScale && session.avatarScale !== 1 ? ` style="scale: ${session.avatarScale}"` : '';
|
||||
const imageHtml = session.image ? `<img src="${session.image}" alt=""${scaleStyle}>` : '';
|
||||
const transformParts = [];
|
||||
if (session.avatarScale && session.avatarScale !== 1) transformParts.push(`scale(${session.avatarScale})`);
|
||||
if (session.avatarOffsetX || session.avatarOffsetY) transformParts.push(`translate(${session.avatarOffsetX || 0}%, ${session.avatarOffsetY || 0}%)`);
|
||||
const transformStyle = transformParts.length ? ` style="transform: ${transformParts.join(' ')}"` : '';
|
||||
const imageHtml = session.image ? `<img src="${session.image}" alt=""${transformStyle}>` : '';
|
||||
|
||||
// Badges
|
||||
const badges = [];
|
||||
@@ -47,8 +50,11 @@ function buildAgentCard(agent, skills) {
|
||||
const card = document.createElement('div');
|
||||
card.className = 'card';
|
||||
|
||||
const scaleStyle = agent.avatarScale && agent.avatarScale !== 1 ? ` style="transform: scale(${agent.avatarScale})"` : '';
|
||||
const imageHtml = agent.avatar ? `<img src="${agent.avatar}" alt=""${scaleStyle}>` : '';
|
||||
const agentTransformParts = [];
|
||||
if (agent.avatarScale && agent.avatarScale !== 1) agentTransformParts.push(`scale(${agent.avatarScale})`);
|
||||
if (agent.avatarOffsetX || agent.avatarOffsetY) agentTransformParts.push(`translate(${agent.avatarOffsetX || 0}%, ${agent.avatarOffsetY || 0}%)`);
|
||||
const agentTransformStyle = agentTransformParts.length ? ` style="transform: ${agentTransformParts.join(' ')}"` : '';
|
||||
const imageHtml = agent.avatar ? `<img src="${agent.avatar}" alt=""${agentTransformStyle}>` : '';
|
||||
|
||||
// Badges
|
||||
const badges = [];
|
||||
|
||||
Reference in new issue
Block a user