Added experimental node - Rayko Text Overlay
This commit is contained in:
@@ -0,0 +1,603 @@
|
||||
import { app } from "../../scripts/app.js";
|
||||
app.registerExtension({
|
||||
name: "RaykoTextOverlay",
|
||||
async beforeRegisterNodeDef(nodeType, nodeData, app) {
|
||||
if (nodeData.name === "RS_TextOverlay") {
|
||||
console.log("🦊 [RS_TextOverlay] JS loaded!");
|
||||
const onNodeCreated = nodeType.prototype.onNodeCreated;
|
||||
nodeType.prototype.onNodeCreated = function() {
|
||||
const result = onNodeCreated ? onNodeCreated.apply(this, arguments) : undefined;
|
||||
this.data = {
|
||||
text: "Текст",
|
||||
font_name: "Arial.ttf",
|
||||
text_color: "#FFFFFF",
|
||||
outline_thickness: 0,
|
||||
outline_color: "#808080",
|
||||
rotate_with_mask: true,
|
||||
text_opacity: 1.0,
|
||||
line_spacing: 1.0,
|
||||
letter_spacing: 0.0,
|
||||
text_orientation: "horizontal",
|
||||
enable_shadow: false,
|
||||
shadow_color: "#333333",
|
||||
shadow_offset_x: 2,
|
||||
shadow_offset_y: 2,
|
||||
shadow_blur: 0.0,
|
||||
shadow_opacity: 0.8,
|
||||
font_list: ["default"]
|
||||
};
|
||||
this.rowHeight = 28;
|
||||
this.padding = 10;
|
||||
this.labelWidth = 120;
|
||||
this.targetWidth = 350;
|
||||
this.clickZones = [];
|
||||
this.widgetYPositions = {};
|
||||
this.hiddenWidget = this.widgets.find(w => w.name === "node_data");
|
||||
if (this.hiddenWidget) {
|
||||
this.hiddenWidget.hidden = true;
|
||||
this.hiddenWidget.serializeValue = () => {
|
||||
this.syncData();
|
||||
return this.hiddenWidget.value;
|
||||
};
|
||||
try {
|
||||
const saved = JSON.parse(this.hiddenWidget.value);
|
||||
if (saved && typeof saved === 'object') {
|
||||
this.data = { ...this.data, ...saved };
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("🦊 [RS_TextOverlay] Error loading saved ", e);
|
||||
}
|
||||
}
|
||||
if (this.widgets) {
|
||||
this.widgets.forEach(w => w.hidden = true);
|
||||
}
|
||||
this.setSize([this.targetWidth, 650]);
|
||||
const self = this;
|
||||
|
||||
this.drawSeparator = function(ctx, text, x, y, w, h) {
|
||||
ctx.fillStyle = "#444";
|
||||
ctx.fillRect(x, y + 8, w, 1);
|
||||
ctx.fillStyle = "#888";
|
||||
ctx.font = "10px sans-serif";
|
||||
ctx.textAlign = "center";
|
||||
ctx.fillText(text, x + w/2, y + 22);
|
||||
};
|
||||
|
||||
this.onDrawForeground = function(ctx, visibleRect) {
|
||||
this.clickZones = [];
|
||||
const startY = 80;
|
||||
const rowH = this.rowHeight;
|
||||
const pad = this.padding;
|
||||
const labelW = this.labelWidth;
|
||||
const arrowW = 25;
|
||||
const inputW = this.size[0] - pad*2 - labelW;
|
||||
let y = startY;
|
||||
|
||||
// === TEXT ===
|
||||
this.drawLabel(ctx, "TEXT", pad, y, labelW, rowH * 2);
|
||||
this.drawMultilineField(ctx, this.data.text, pad + labelW, y, inputW, rowH * 2);
|
||||
this.clickZones.push({ type: "text", x: pad + labelW, y: y, w: inputW, h: rowH * 2 });
|
||||
y += rowH * 2 + 5;
|
||||
|
||||
// === FONT_NAME ===
|
||||
this.drawLabel(ctx, "FONT", pad, y, labelW, rowH);
|
||||
this.drawComboField(ctx, this.data.font_name, pad + labelW, y, inputW, rowH);
|
||||
this.widgetYPositions["font_name"] = y;
|
||||
this.clickZones.push({ type: "combo", field: "font_name", x: pad + labelW, y: y, w: inputW, h: rowH });
|
||||
y += rowH + 5;
|
||||
|
||||
// === TEXT_COLOR ===
|
||||
this.drawLabel(ctx, "TEXT COLOR", pad, y, labelW, rowH);
|
||||
this.drawColorField(ctx, this.data.text_color, pad + labelW, y, inputW, rowH);
|
||||
this.widgetYPositions["text_color"] = y;
|
||||
this.clickZones.push({ type: "color", field: "text_color", x: pad + labelW, y: y, w: inputW, h: rowH });
|
||||
y += rowH + 5;
|
||||
|
||||
// === OUTLINE_THICKNESS ===
|
||||
this.drawLabel(ctx, "OUTLINE THICK", pad, y, labelW, rowH);
|
||||
this.drawNumberFieldWithArrows(ctx, this.data.outline_thickness, pad + labelW, y, inputW, rowH, arrowW);
|
||||
this.widgetYPositions["outline_thickness"] = y;
|
||||
this.clickZones.push({ type: "number", field: "outline_thickness", x: pad + labelW + arrowW, y: y, w: inputW - arrowW*2, h: rowH, min: 0, max: 50 });
|
||||
this.clickZones.push({ type: "arrow_left", field: "outline_thickness", x: pad + labelW, y: y, w: arrowW, h: rowH, min: 0, max: 50, step: 1 });
|
||||
this.clickZones.push({ type: "arrow_right", field: "outline_thickness", x: pad + labelW + inputW - arrowW, y: y, w: arrowW, h: rowH, min: 0, max: 50, step: 1 });
|
||||
y += rowH + 5;
|
||||
|
||||
// === OUTLINE_COLOR ===
|
||||
this.drawLabel(ctx, "OUTLINE COLOR", pad, y, labelW, rowH);
|
||||
this.drawColorField(ctx, this.data.outline_color, pad + labelW, y, inputW, rowH);
|
||||
this.widgetYPositions["outline_color"] = y;
|
||||
this.clickZones.push({ type: "color", field: "outline_color", x: pad + labelW, y: y, w: inputW, h: rowH });
|
||||
y += rowH + 5;
|
||||
|
||||
// === FORMATTING TEXT ===
|
||||
this.drawSeparator(ctx, "FORMATTING TEXT", pad, y, inputW + labelW, rowH);
|
||||
y += rowH + 5;
|
||||
|
||||
// === ROTATE_WITH_MASK ===
|
||||
this.drawLabel(ctx, "ROTATE WITH MASK", pad, y, labelW, rowH);
|
||||
this.drawBooleanField(ctx, this.data.rotate_with_mask, pad + labelW, y, inputW, rowH);
|
||||
this.widgetYPositions["rotate_with_mask"] = y;
|
||||
this.clickZones.push({ type: "boolean", field: "rotate_with_mask", x: pad + labelW, y: y, w: inputW, h: rowH });
|
||||
y += rowH + 5;
|
||||
|
||||
// === TEXT_OPACITY ===
|
||||
this.drawLabel(ctx, "TEXT OPACITY", pad, y, labelW, rowH);
|
||||
this.drawNumberFieldWithArrows(ctx, this.data.text_opacity, pad + labelW, y, inputW, rowH, arrowW);
|
||||
this.widgetYPositions["text_opacity"] = y;
|
||||
this.clickZones.push({ type: "number", field: "text_opacity", x: pad + labelW + arrowW, y: y, w: inputW - arrowW*2, h: rowH, min: 0, max: 1, float: true });
|
||||
this.clickZones.push({ type: "arrow_left", field: "text_opacity", x: pad + labelW, y: y, w: arrowW, h: rowH, min: 0, max: 1, step: 0.05, float: true });
|
||||
this.clickZones.push({ type: "arrow_right", field: "text_opacity", x: pad + labelW + inputW - arrowW, y: y, w: arrowW, h: rowH, min: 0, max: 1, step: 0.05, float: true });
|
||||
y += rowH + 5;
|
||||
|
||||
// === LINE_SPACING ===
|
||||
this.drawLabel(ctx, "LINE SPACING", pad, y, labelW, rowH);
|
||||
this.drawNumberFieldWithArrows(ctx, this.data.line_spacing, pad + labelW, y, inputW, rowH, arrowW);
|
||||
this.widgetYPositions["line_spacing"] = y;
|
||||
this.clickZones.push({ type: "number", field: "line_spacing", x: pad + labelW + arrowW, y: y, w: inputW - arrowW*2, h: rowH, min: 0.5, max: 3, float: true });
|
||||
this.clickZones.push({ type: "arrow_left", field: "line_spacing", x: pad + labelW, y: y, w: arrowW, h: rowH, min: 0.5, max: 3, step: 0.1, float: true });
|
||||
this.clickZones.push({ type: "arrow_right", field: "line_spacing", x: pad + labelW + inputW - arrowW, y: y, w: arrowW, h: rowH, min: 0.5, max: 3, step: 0.1, float: true });
|
||||
y += rowH + 5;
|
||||
|
||||
// === LETTER_SPACING ===
|
||||
this.drawLabel(ctx, "LETTER SPACING", pad, y, labelW, rowH);
|
||||
this.drawNumberFieldWithArrows(ctx, this.data.letter_spacing, pad + labelW, y, inputW, rowH, arrowW);
|
||||
this.widgetYPositions["letter_spacing"] = y;
|
||||
this.clickZones.push({ type: "number", field: "letter_spacing", x: pad + labelW + arrowW, y: y, w: inputW - arrowW*2, h: rowH, min: -20, max: 100, float: true });
|
||||
this.clickZones.push({ type: "arrow_left", field: "letter_spacing", x: pad + labelW, y: y, w: arrowW, h: rowH, min: -20, max: 100, step: 0.5, float: true });
|
||||
this.clickZones.push({ type: "arrow_right", field: "letter_spacing", x: pad + labelW + inputW - arrowW, y: y, w: arrowW, h: rowH, min: -20, max: 100, step: 0.5, float: true });
|
||||
y += rowH + 5;
|
||||
|
||||
// === TEXT_ORIENTATION ===
|
||||
this.drawLabel(ctx, "ORIENTATION", pad, y, labelW, rowH);
|
||||
this.drawComboField(ctx, this.data.text_orientation, pad + labelW, y, inputW, rowH);
|
||||
this.widgetYPositions["text_orientation"] = y;
|
||||
this.clickZones.push({ type: "combo", field: "text_orientation", x: pad + labelW, y: y, w: inputW, h: rowH });
|
||||
y += rowH + 5;
|
||||
|
||||
// === SETTING SHADOW ===
|
||||
this.drawSeparator(ctx, "SETTING SHADOW", pad, y, inputW + labelW, rowH);
|
||||
y += rowH + 5;
|
||||
|
||||
// === ENABLE_SHADOW ===
|
||||
this.drawLabel(ctx, "ENABLE SHADOW", pad, y, labelW, rowH);
|
||||
this.drawBooleanField(ctx, this.data.enable_shadow, pad + labelW, y, inputW, rowH);
|
||||
this.widgetYPositions["enable_shadow"] = y;
|
||||
this.clickZones.push({ type: "boolean", field: "enable_shadow", x: pad + labelW, y: y, w: inputW, h: rowH });
|
||||
y += rowH + 5;
|
||||
|
||||
// === SHADOW_COLOR ===
|
||||
this.drawLabel(ctx, "SHADOW COLOR", pad, y, labelW, rowH);
|
||||
this.drawColorField(ctx, this.data.shadow_color, pad + labelW, y, inputW, rowH);
|
||||
this.widgetYPositions["shadow_color"] = y;
|
||||
this.clickZones.push({ type: "color", field: "shadow_color", x: pad + labelW, y: y, w: inputW, h: rowH });
|
||||
y += rowH + 5;
|
||||
|
||||
// === SHADOW_OFFSET_X ===
|
||||
this.drawLabel(ctx, "SHADOW OFFSET X", pad, y, labelW, rowH);
|
||||
this.drawNumberFieldWithArrows(ctx, this.data.shadow_offset_x, pad + labelW, y, inputW, rowH, arrowW);
|
||||
this.widgetYPositions["shadow_offset_x"] = y;
|
||||
this.clickZones.push({ type: "number", field: "shadow_offset_x", x: pad + labelW + arrowW, y: y, w: inputW - arrowW*2, h: rowH, min: -50, max: 50 });
|
||||
this.clickZones.push({ type: "arrow_left", field: "shadow_offset_x", x: pad + labelW, y: y, w: arrowW, h: rowH, min: -50, max: 50, step: 1 });
|
||||
this.clickZones.push({ type: "arrow_right", field: "shadow_offset_x", x: pad + labelW + inputW - arrowW, y: y, w: arrowW, h: rowH, min: -50, max: 50, step: 1 });
|
||||
y += rowH + 5;
|
||||
|
||||
// === SHADOW_OFFSET_Y ===
|
||||
this.drawLabel(ctx, "SHADOW OFFSET Y", pad, y, labelW, rowH);
|
||||
this.drawNumberFieldWithArrows(ctx, this.data.shadow_offset_y, pad + labelW, y, inputW, rowH, arrowW);
|
||||
this.widgetYPositions["shadow_offset_y"] = y;
|
||||
this.clickZones.push({ type: "number", field: "shadow_offset_y", x: pad + labelW + arrowW, y: y, w: inputW - arrowW*2, h: rowH, min: -50, max: 50 });
|
||||
this.clickZones.push({ type: "arrow_left", field: "shadow_offset_y", x: pad + labelW, y: y, w: arrowW, h: rowH, min: -50, max: 50, step: 1 });
|
||||
this.clickZones.push({ type: "arrow_right", field: "shadow_offset_y", x: pad + labelW + inputW - arrowW, y: y, w: arrowW, h: rowH, min: -50, max: 50, step: 1 });
|
||||
y += rowH + 5;
|
||||
|
||||
// === SHADOW_BLUR ===
|
||||
this.drawLabel(ctx, "SHADOW BLUR", pad, y, labelW, rowH);
|
||||
this.drawNumberFieldWithArrows(ctx, this.data.shadow_blur, pad + labelW, y, inputW, rowH, arrowW);
|
||||
this.widgetYPositions["shadow_blur"] = y;
|
||||
this.clickZones.push({ type: "number", field: "shadow_blur", x: pad + labelW + arrowW, y: y, w: inputW - arrowW*2, h: rowH, min: 0, max: 1, float: true });
|
||||
this.clickZones.push({ type: "arrow_left", field: "shadow_blur", x: pad + labelW, y: y, w: arrowW, h: rowH, min: 0, max: 1, step: 0.05, float: true });
|
||||
this.clickZones.push({ type: "arrow_right", field: "shadow_blur", x: pad + labelW + inputW - arrowW, y: y, w: arrowW, h: rowH, min: 0, max: 1, step: 0.05, float: true });
|
||||
y += rowH + 5;
|
||||
|
||||
// === SHADOW_OPACITY ===
|
||||
this.drawLabel(ctx, "SHADOW OPACITY", pad, y, labelW, rowH);
|
||||
this.drawNumberFieldWithArrows(ctx, this.data.shadow_opacity, pad + labelW, y, inputW, rowH, arrowW);
|
||||
this.widgetYPositions["shadow_opacity"] = y;
|
||||
this.clickZones.push({ type: "number", field: "shadow_opacity", x: pad + labelW + arrowW, y: y, w: inputW - arrowW*2, h: rowH, min: 0, max: 1, float: true });
|
||||
this.clickZones.push({ type: "arrow_left", field: "shadow_opacity", x: pad + labelW, y: y, w: arrowW, h: rowH, min: 0, max: 1, step: 0.05, float: true });
|
||||
this.clickZones.push({ type: "arrow_right", field: "shadow_opacity", x: pad + labelW + inputW - arrowW, y: y, w: arrowW, h: rowH, min: 0, max: 1, step: 0.05, float: true });
|
||||
y += rowH + 15;
|
||||
|
||||
const totalH = y + 10;
|
||||
if (this.size[1] < totalH) {
|
||||
this.setSize([this.targetWidth, totalH]);
|
||||
}
|
||||
};
|
||||
|
||||
this.onMouseDown = function(e, pos, canvas) {
|
||||
if (!this.clickZones.length) return false;
|
||||
for (const zone of this.clickZones) {
|
||||
const inX = pos[0] >= zone.x && pos[0] <= zone.x + zone.w;
|
||||
const inY = pos[1] >= zone.y && pos[1] <= zone.y + zone.h;
|
||||
if (inX && inY) {
|
||||
if (zone.type === "text") {
|
||||
self.showTextInput();
|
||||
return true;
|
||||
}
|
||||
if (zone.type === "combo") {
|
||||
console.log("🔽 Combo clicked:", zone.field);
|
||||
self.showComboSelector(zone.field);
|
||||
return true;
|
||||
}
|
||||
if (zone.type === "color") {
|
||||
console.log("🎨 Color clicked:", zone.field);
|
||||
self.openColorPicker(zone.field);
|
||||
return true;
|
||||
}
|
||||
if (zone.type === "boolean") {
|
||||
self.data[zone.field] = !self.data[zone.field];
|
||||
self.updateUI();
|
||||
return true;
|
||||
}
|
||||
if (zone.type === "number") {
|
||||
const current = self.data[zone.field];
|
||||
const newVal = prompt(`${zone.field}:`, current);
|
||||
if (newVal !== null) {
|
||||
let parsed = zone.float ? parseFloat(newVal) : parseInt(newVal);
|
||||
if (!isNaN(parsed)) {
|
||||
parsed = Math.max(zone.min, Math.min(zone.max, parsed));
|
||||
self.data[zone.field] = zone.float ? Math.round(parsed * 100) / 100 : parsed;
|
||||
self.updateUI();
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if (zone.type === "arrow_left") {
|
||||
let current = self.data[zone.field];
|
||||
current = zone.float ? current - zone.step : current - zone.step;
|
||||
current = zone.float ? Math.round(current * 100) / 100 : current;
|
||||
self.data[zone.field] = Math.max(zone.min, current);
|
||||
self.updateUI();
|
||||
return true;
|
||||
}
|
||||
if (zone.type === "arrow_right") {
|
||||
let current = self.data[zone.field];
|
||||
current = zone.float ? current + zone.step : current + zone.step;
|
||||
current = zone.float ? Math.round(current * 100) / 100 : current;
|
||||
self.data[zone.field] = Math.min(zone.max, current);
|
||||
self.updateUI();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
this.drawLabel = function(ctx, text, x, y, w, h) {
|
||||
ctx.fillStyle = "#aaa";
|
||||
ctx.font = "11px sans-serif";
|
||||
ctx.textAlign = "left";
|
||||
ctx.fillText(text, x, y + h/2 + 4);
|
||||
};
|
||||
|
||||
this.drawMultilineField = function(ctx, value, x, y, w, h) {
|
||||
ctx.fillStyle = "#222";
|
||||
ctx.fillRect(x, y, w, h);
|
||||
ctx.strokeStyle = "#444";
|
||||
ctx.strokeRect(x, y, w, h);
|
||||
ctx.fillStyle = "#fff";
|
||||
ctx.font = "11px sans-serif";
|
||||
ctx.textAlign = "left";
|
||||
const display = value.length > 30 ? value.substring(0, 27) + "..." : value;
|
||||
ctx.fillText(display.replace('\n', '\\n'), x + 5, y + h/2 + 4);
|
||||
};
|
||||
|
||||
this.drawComboField = function(ctx, value, x, y, w, h) {
|
||||
ctx.fillStyle = "#222";
|
||||
ctx.fillRect(x, y, w, h);
|
||||
ctx.strokeStyle = "#444";
|
||||
ctx.strokeRect(x, y, w, h);
|
||||
ctx.fillStyle = "#fff";
|
||||
ctx.font = "11px sans-serif";
|
||||
ctx.textAlign = "center";
|
||||
ctx.fillText(value, x + w/2, y + h/2 + 4);
|
||||
ctx.fillStyle = "#666";
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(x + w - 12, y + h/2 - 3);
|
||||
ctx.lineTo(x + w - 6, y + h/2 - 3);
|
||||
ctx.lineTo(x + w - 9, y + h/2 + 3);
|
||||
ctx.fill();
|
||||
};
|
||||
|
||||
this.drawColorField = function(ctx, value, x, y, w, h) {
|
||||
const hexW = w - 25;
|
||||
ctx.fillStyle = "#222";
|
||||
ctx.fillRect(x, y, hexW, h);
|
||||
ctx.strokeStyle = "#444";
|
||||
ctx.strokeRect(x, y, hexW, h);
|
||||
ctx.fillStyle = "#fff";
|
||||
ctx.font = "11px sans-serif";
|
||||
ctx.textAlign = "center";
|
||||
ctx.fillText(value, x + hexW/2, y + h/2 + 4);
|
||||
const colorBoxX = x + hexW + 5;
|
||||
const colorBoxY = y + 0;
|
||||
const colorBoxH = h + 0;
|
||||
ctx.fillStyle = value;
|
||||
ctx.fillRect(colorBoxX, colorBoxY, 20, colorBoxH);
|
||||
ctx.strokeStyle = "#fff";
|
||||
ctx.lineWidth = 1.5;
|
||||
ctx.strokeRect(colorBoxX, colorBoxY, 20, colorBoxH);
|
||||
};
|
||||
|
||||
this.drawNumberFieldWithArrows = function(ctx, value, x, y, w, h, arrowW) {
|
||||
ctx.fillStyle = "#222";
|
||||
ctx.fillRect(x, y, w, h);
|
||||
ctx.strokeStyle = "#444";
|
||||
ctx.strokeRect(x, y, w, h);
|
||||
ctx.fillStyle = "#4CAF50";
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(x + 8, y + h/2);
|
||||
ctx.lineTo(x + 16, y + h/2 - 6);
|
||||
ctx.lineTo(x + 16, y + h/2 + 6);
|
||||
ctx.closePath();
|
||||
ctx.fill();
|
||||
ctx.fillStyle = "#4CAF50";
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(x + w - 8, y + h/2);
|
||||
ctx.lineTo(x + w - 16, y + h/2 - 6);
|
||||
ctx.lineTo(x + w - 16, y + h/2 + 6);
|
||||
ctx.closePath();
|
||||
ctx.fill();
|
||||
ctx.fillStyle = "#fff";
|
||||
ctx.font = "11px sans-serif";
|
||||
ctx.textAlign = "center";
|
||||
const displayValue = typeof value === 'number' && value % 1 !== 0 ? value.toFixed(2) : value;
|
||||
ctx.fillText(displayValue, x + w/2, y + h/2 + 4);
|
||||
};
|
||||
|
||||
this.drawBooleanField = function(ctx, value, x, y, w, h) {
|
||||
ctx.fillStyle = "#222";
|
||||
ctx.fillRect(x, y, w, h);
|
||||
ctx.strokeStyle = "#444";
|
||||
ctx.strokeRect(x, y, w, h);
|
||||
const circleX = x + w - 35;
|
||||
const circleY = y + h/2;
|
||||
ctx.fillStyle = value ? "#4CAF50" : "#555";
|
||||
ctx.beginPath();
|
||||
ctx.arc(circleX, circleY, 8, 0, Math.PI * 2);
|
||||
ctx.fill();
|
||||
ctx.fillStyle = "#fff";
|
||||
ctx.font = "11px sans-serif";
|
||||
ctx.textAlign = "left";
|
||||
ctx.fillText(value ? "ON" : "OFF", x + 8, y + h/2 + 4);
|
||||
};
|
||||
|
||||
this.showComboSelector = function(fieldName) {
|
||||
const options = {
|
||||
font_name: self.data.font_list || ["default"],
|
||||
vertical_align: ["top", "center", "bottom"],
|
||||
horizontal_align: ["left", "center", "right"],
|
||||
text_orientation: ["horizontal", "vertical"]
|
||||
};
|
||||
const list = options[fieldName] || [];
|
||||
if (!list.length) return;
|
||||
const menu = document.createElement("div");
|
||||
menu.style.cssText = `
|
||||
position: fixed;
|
||||
background: #1a1a1a;
|
||||
border: 1px solid #444;
|
||||
border-radius: 6px;
|
||||
max-height: 300px;
|
||||
overflow-y: auto;
|
||||
z-index: 10001;
|
||||
box-shadow: 0 4px 20px rgba(0,0,0,0.5);
|
||||
min-width: 150px;
|
||||
`;
|
||||
list.forEach(opt => {
|
||||
const item = document.createElement("div");
|
||||
item.textContent = opt;
|
||||
item.style.cssText = `
|
||||
padding: 10px 15px;
|
||||
cursor: pointer;
|
||||
color: #ddd;
|
||||
font-size: 12px;
|
||||
border-bottom: 1px solid #333;
|
||||
`;
|
||||
item.onmouseover = () => item.style.background = "#333";
|
||||
item.onmouseout = () => item.style.background = "#1a1a1a";
|
||||
item.onclick = (e) => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
console.log("✅ Combo selected:", opt);
|
||||
self.data[fieldName] = opt;
|
||||
self.updateUI();
|
||||
menu.remove();
|
||||
};
|
||||
menu.appendChild(item);
|
||||
});
|
||||
const widgetY = self.widgetYPositions[fieldName] || 200;
|
||||
const canvasRect = document.querySelector("#graph-canvas")?.getBoundingClientRect() ||
|
||||
document.querySelector("canvas")?.getBoundingClientRect();
|
||||
if (canvasRect && self.pos) {
|
||||
const menuX = canvasRect.left + self.pos[0] + self.padding + self.labelWidth;
|
||||
const menuY = canvasRect.top + self.pos[1] + widgetY + self.rowHeight;
|
||||
menu.style.left = menuX + "px";
|
||||
menu.style.top = menuY + "px";
|
||||
} else {
|
||||
menu.style.left = "250px";
|
||||
menu.style.top = "200px";
|
||||
}
|
||||
document.body.appendChild(menu);
|
||||
setTimeout(() => {
|
||||
const closeHandler = (e) => {
|
||||
if (!menu.contains(e.target)) {
|
||||
menu.remove();
|
||||
document.removeEventListener("mousedown", closeHandler);
|
||||
}
|
||||
};
|
||||
document.addEventListener("mousedown", closeHandler);
|
||||
}, 100);
|
||||
};
|
||||
|
||||
this.showTextInput = function() {
|
||||
const currentValue = self.data.text || '';
|
||||
|
||||
const popup = document.createElement('div');
|
||||
popup.style.cssText = `
|
||||
position: fixed;
|
||||
z-index: 10002;
|
||||
background: #1a1a1a;
|
||||
border: 1px solid #444;
|
||||
border-radius: 6px;
|
||||
padding: 10px;
|
||||
box-shadow: 0 4px 20px rgba(0,0,0,0.5);
|
||||
`;
|
||||
|
||||
const input = document.createElement('textarea');
|
||||
input.value = currentValue;
|
||||
input.style.cssText = `
|
||||
width: 300px;
|
||||
height: 150px;
|
||||
background: #222;
|
||||
color: #fff;
|
||||
border: 1px solid #444;
|
||||
border-radius: 4px;
|
||||
padding: 10px;
|
||||
font-size: 12px;
|
||||
resize: none;
|
||||
display: block;
|
||||
margin-bottom: 10px;
|
||||
`;
|
||||
|
||||
const saveBtn = document.createElement('button');
|
||||
saveBtn.textContent = '✅';
|
||||
saveBtn.style.cssText = `
|
||||
background: #4CAF50;
|
||||
color: #fff;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
padding: 8px 16px;
|
||||
font-size: 14px;
|
||||
cursor: pointer;
|
||||
float: right;
|
||||
`;
|
||||
saveBtn.onmouseover = () => saveBtn.style.background = "#45a049";
|
||||
saveBtn.onmouseout = () => saveBtn.style.background = "#4CAF50";
|
||||
|
||||
popup.appendChild(input);
|
||||
popup.appendChild(saveBtn);
|
||||
|
||||
const widgetY = self.widgetYPositions["text"] || 80;
|
||||
const canvasRect = document.querySelector("#graph-canvas")?.getBoundingClientRect();
|
||||
if (canvasRect && self.pos) {
|
||||
const inputX = canvasRect.left + self.pos[0] + self.padding + self.labelWidth;
|
||||
const inputY = canvasRect.top + self.pos[1] + widgetY + (self.rowHeight * 2);
|
||||
popup.style.left = inputX + 'px';
|
||||
popup.style.top = inputY + 'px';
|
||||
}
|
||||
|
||||
document.body.appendChild(popup);
|
||||
input.focus();
|
||||
|
||||
saveBtn.onclick = (e) => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
self.data.text = input.value;
|
||||
self.updateUI();
|
||||
popup.remove();
|
||||
};
|
||||
};
|
||||
|
||||
this.openColorPicker = function(fieldName) {
|
||||
const currentColor = self.data[fieldName] || '#FFFFFF';
|
||||
console.log("🎨 Opening color picker:", fieldName, currentColor);
|
||||
const colorInput = document.createElement('input');
|
||||
colorInput.type = 'color';
|
||||
colorInput.value = currentColor;
|
||||
colorInput.style.display = 'none';
|
||||
document.body.appendChild(colorInput);
|
||||
colorInput.addEventListener('change', (e) => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
const newColor = e.target.value.toUpperCase();
|
||||
self.data[fieldName] = newColor;
|
||||
console.log("🎨 Color changed:", newColor);
|
||||
self.updateUI();
|
||||
}, { once: false });
|
||||
const widgetY = self.widgetYPositions[fieldName] || 200;
|
||||
const canvasRect = document.querySelector("#graph-canvas")?.getBoundingClientRect() ||
|
||||
document.querySelector("canvas")?.getBoundingClientRect();
|
||||
if (canvasRect && self.pos) {
|
||||
const pickerX = canvasRect.left + self.pos[0] + self.padding + self.labelWidth;
|
||||
const pickerY = canvasRect.top + self.pos[1] + widgetY;
|
||||
colorInput.style.position = 'fixed';
|
||||
colorInput.style.left = pickerX + 'px';
|
||||
colorInput.style.top = pickerY + 'px';
|
||||
colorInput.style.zIndex = '10002';
|
||||
colorInput.style.width = '1px';
|
||||
colorInput.style.height = '1px';
|
||||
colorInput.style.opacity = '0';
|
||||
console.log("📍 Picker positioned at:", pickerX, pickerY);
|
||||
requestAnimationFrame(() => {
|
||||
if (colorInput.showPicker) {
|
||||
colorInput.showPicker();
|
||||
} else {
|
||||
colorInput.click();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
colorInput.style.position = 'fixed';
|
||||
colorInput.style.left = (window.innerWidth / 2) + 'px';
|
||||
colorInput.style.top = (window.innerHeight / 2) + 'px';
|
||||
colorInput.style.zIndex = '10002';
|
||||
requestAnimationFrame(() => {
|
||||
if (colorInput.showPicker) {
|
||||
colorInput.showPicker();
|
||||
} else {
|
||||
colorInput.click();
|
||||
}
|
||||
});
|
||||
}
|
||||
const clickOutsideHandler = (e) => {
|
||||
if (e.target !== colorInput) {
|
||||
colorInput.remove();
|
||||
document.removeEventListener("mousedown", clickOutsideHandler);
|
||||
}
|
||||
};
|
||||
setTimeout(() => {
|
||||
document.addEventListener("mousedown", clickOutsideHandler);
|
||||
}, 200);
|
||||
};
|
||||
|
||||
this.syncData = function() {
|
||||
if (this.hiddenWidget) {
|
||||
this.hiddenWidget.value = JSON.stringify(self.data, null, 2)
|
||||
.replace(/\\u([0-9a-fA-F]{4})/g, function(match, p1) {
|
||||
return String.fromCharCode(parseInt(p1, 16));
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
this.updateUI = function() {
|
||||
self.syncData();
|
||||
if (self.graph) self.graph.setDirtyCanvas(true, true);
|
||||
};
|
||||
|
||||
const onSerialize = this.onSerialize;
|
||||
this.onSerialize = function(o) {
|
||||
self.syncData();
|
||||
return onSerialize ? onSerialize.apply(this, arguments) : undefined;
|
||||
};
|
||||
|
||||
const onExecute = this.onExecute;
|
||||
this.onExecute = function() {
|
||||
self.syncData();
|
||||
return onExecute ? onExecute.apply(this, arguments) : undefined;
|
||||
};
|
||||
|
||||
return result;
|
||||
};
|
||||
}
|
||||
}
|
||||
});
|
||||
console.log("🦊 [RS_TextOverlay] Extension initialized");
|
||||
Reference in New Issue
Block a user