- Add Ultralytics 21-keypoint hand insertion, transforms, focused editing, and rendering. - Upgrade gallery grouping, search, previews, pose libraries, and localized UI. - Add render controls, update notifications, documentation, and regression coverage.
651 lines
25 KiB
JavaScript
651 lines
25 KiB
JavaScript
import { t } from "./i18n.js";
|
|
import { registerModule } from "./index.js";
|
|
import { applySidebarButtonStyles } from "../utils.js";
|
|
import { getPersistedJSON, setPersistedJSON } from "../utils.js";
|
|
import { UiIcons } from "../ui-icons.js";
|
|
|
|
const RENDER_STYLE_STORAGE_KEY = "ultralytics_render_style";
|
|
const RENDER_STYLE_VERSION = 2;
|
|
const RENDER_STYLE_SYNC_URL = "/ultralytics/render_style";
|
|
|
|
const RENDER_STYLE_KEYS = {
|
|
version: "comfyui_ultralytics_editor.renderer.version",
|
|
body: {
|
|
lineWidth: "comfyui_ultralytics_editor.renderer.body.line_width",
|
|
keypointColor: "comfyui_ultralytics_editor.renderer.body.keypoint_color",
|
|
keypointRadius: "comfyui_ultralytics_editor.renderer.body.keypoint_radius"
|
|
},
|
|
hands: {
|
|
lineWidth: "comfyui_ultralytics_editor.renderer.hands.line_width",
|
|
keypointColor: "comfyui_ultralytics_editor.renderer.hands.keypoint_color",
|
|
keypointRadius: "comfyui_ultralytics_editor.renderer.hands.keypoint_radius"
|
|
},
|
|
face: {
|
|
lineWidth: "comfyui_ultralytics_editor.renderer.face.line_width",
|
|
keypointColor: "comfyui_ultralytics_editor.renderer.face.keypoint_color",
|
|
keypointRadius: "comfyui_ultralytics_editor.renderer.face.keypoint_radius"
|
|
}
|
|
};
|
|
|
|
const LINE_WIDTH_RANGE = { min: 0, max: 12 };
|
|
const KEYPOINT_RADIUS_RANGE = { min: 0, max: 24 };
|
|
const DISPLAY_COLOR_FALLBACKS = {
|
|
body: [255, 0, 0, 255],
|
|
hands: [255, 0, 0, 255],
|
|
face: [255, 255, 255, 255]
|
|
};
|
|
|
|
export function buildRenderStylePanelHtml() {
|
|
return `
|
|
<div class="openpose-overlay openpose-render-style-overlay" data-overlay="render-style">
|
|
<div class="openpose-overlay-card openpose-render-style-card">
|
|
<div class="openpose-overlay-content openpose-render-style-content">
|
|
<div class="openpose-render-style-header">
|
|
<div class="openpose-render-style-intro">${t("render_style.intro")}</div>
|
|
<div class="openpose-render-style-actions">
|
|
<button class="openpose-btn openpose-render-style-reset" data-action="render-style-reset">\u{267B}\u{FE0F} ${t("render_style.btn.reset")}</button>
|
|
</div>
|
|
</div>
|
|
<div class="openpose-render-style-controls-wrapper">
|
|
<div class="openpose-render-style-sections">
|
|
<div class="openpose-render-style-section" data-render-section="body">
|
|
<div class="openpose-render-style-section-title">${t("render_style.section.body")}</div>
|
|
<div class="openpose-render-style-grid">
|
|
<label class="openpose-render-style-label">${t("render_style.field.line_width")}</label>
|
|
<div class="openpose-render-style-input-row"><input class="openpose-input openpose-render-style-number" type="number" min="0" max="12" step="1" data-render-field="line_width" /><span class="openpose-render-style-unit">${t("render_style.unit.px")}</span></div>
|
|
<label class="openpose-render-style-label">${t("render_style.field.keypoint_radius")}</label>
|
|
<div class="openpose-render-style-input-row"><input class="openpose-input openpose-render-style-number" type="number" min="0" max="24" step="1" data-render-field="keypoint_radius" /><span class="openpose-render-style-unit">${t("render_style.unit.px")}</span></div>
|
|
</div>
|
|
</div>
|
|
<div class="openpose-render-style-section" data-render-section="hands">
|
|
<div class="openpose-render-style-section-title">${t("render_style.section.hands")}</div>
|
|
<div class="openpose-render-style-grid">
|
|
<label class="openpose-render-style-label">${t("render_style.field.line_width")}</label>
|
|
<div class="openpose-render-style-input-row"><input class="openpose-input openpose-render-style-number" type="number" min="0" max="12" step="1" data-render-field="line_width" /><span class="openpose-render-style-unit">${t("render_style.unit.px")}</span></div>
|
|
<label class="openpose-render-style-label">${t("render_style.field.keypoint_radius")}</label>
|
|
<div class="openpose-render-style-input-row"><input class="openpose-input openpose-render-style-number" type="number" min="0" max="24" step="1" data-render-field="keypoint_radius" /><span class="openpose-render-style-unit">${t("render_style.unit.px")}</span></div>
|
|
<label class="openpose-render-style-label">${t("render_style.field.keypoint_color")}</label>
|
|
<div class="openpose-render-style-color-row">
|
|
<input class="openpose-render-style-color" type="color" data-render-field="color_picker" />
|
|
<div class="openpose-render-style-channel">
|
|
<span class="openpose-render-style-channel-label">R</span>
|
|
<input class="openpose-input openpose-render-style-channel-input" type="number" min="0" max="255" step="1" data-render-channel="r" />
|
|
</div>
|
|
<div class="openpose-render-style-channel">
|
|
<span class="openpose-render-style-channel-label">G</span>
|
|
<input class="openpose-input openpose-render-style-channel-input" type="number" min="0" max="255" step="1" data-render-channel="g" />
|
|
</div>
|
|
<div class="openpose-render-style-channel">
|
|
<span class="openpose-render-style-channel-label">B</span>
|
|
<input class="openpose-input openpose-render-style-channel-input" type="number" min="0" max="255" step="1" data-render-channel="b" />
|
|
</div>
|
|
<div class="openpose-render-style-channel">
|
|
<span class="openpose-render-style-channel-label">A</span>
|
|
<input class="openpose-input openpose-render-style-channel-input" type="number" min="0" max="255" step="1" data-render-channel="a" />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="openpose-render-style-section" data-render-section="face">
|
|
<div class="openpose-render-style-section-title">${t("render_style.section.face")}</div>
|
|
<div class="openpose-render-style-grid">
|
|
<label class="openpose-render-style-label">${t("render_style.field.line_width")}</label>
|
|
<div class="openpose-render-style-input-row"><input class="openpose-input openpose-render-style-number" type="number" min="0" max="12" step="1" data-render-field="line_width" /><span class="openpose-render-style-unit">${t("render_style.unit.px")}</span></div>
|
|
<label class="openpose-render-style-label">${t("render_style.field.keypoint_radius")}</label>
|
|
<div class="openpose-render-style-input-row"><input class="openpose-input openpose-render-style-number" type="number" min="0" max="24" step="1" data-render-field="keypoint_radius" /><span class="openpose-render-style-unit">${t("render_style.unit.px")}</span></div>
|
|
<label class="openpose-render-style-label">${t("render_style.field.keypoint_color")}</label>
|
|
<div class="openpose-render-style-color-row">
|
|
<input class="openpose-render-style-color" type="color" data-render-field="color_picker" />
|
|
<div class="openpose-render-style-channel">
|
|
<span class="openpose-render-style-channel-label">R</span>
|
|
<input class="openpose-input openpose-render-style-channel-input" type="number" min="0" max="255" step="1" data-render-channel="r" />
|
|
</div>
|
|
<div class="openpose-render-style-channel">
|
|
<span class="openpose-render-style-channel-label">G</span>
|
|
<input class="openpose-input openpose-render-style-channel-input" type="number" min="0" max="255" step="1" data-render-channel="g" />
|
|
</div>
|
|
<div class="openpose-render-style-channel">
|
|
<span class="openpose-render-style-channel-label">B</span>
|
|
<input class="openpose-input openpose-render-style-channel-input" type="number" min="0" max="255" step="1" data-render-channel="b" />
|
|
</div>
|
|
<div class="openpose-render-style-channel">
|
|
<span class="openpose-render-style-channel-label">A</span>
|
|
<input class="openpose-input openpose-render-style-channel-input" type="number" min="0" max="255" step="1" data-render-channel="a" />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="openpose-render-style-note">${t("render_style.storage_note")}</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
function clampNumber(value, min, max, fallback) {
|
|
const num = Number(value);
|
|
if (!Number.isFinite(num)) {
|
|
return fallback;
|
|
}
|
|
return Math.min(max, Math.max(min, num));
|
|
}
|
|
|
|
function normalizeColorArray(value, fallback) {
|
|
if (value === null && fallback === null) {
|
|
return null;
|
|
}
|
|
const source = Array.isArray(value) ? value : [];
|
|
const base = Array.isArray(fallback) ? fallback : [0, 0, 0, 255];
|
|
const output = [];
|
|
for (let i = 0; i < 4; i += 1) {
|
|
const fallbackValue = Number.isFinite(base[i]) ? base[i] : (i === 3 ? 255 : 0);
|
|
const candidate = Number.isFinite(Number(source[i])) ? Number(source[i]) : fallbackValue;
|
|
output.push(clampNumber(candidate, 0, 255, fallbackValue));
|
|
}
|
|
return output;
|
|
}
|
|
|
|
function normalizeSectionSettings(payload, defaults, keyMap) {
|
|
const rawLineWidth = payload[keyMap.lineWidth];
|
|
const rawKeypointRadius = payload[keyMap.keypointRadius];
|
|
const settings = {
|
|
lineWidth: Math.round(clampNumber(rawLineWidth, LINE_WIDTH_RANGE.min, LINE_WIDTH_RANGE.max, defaults.lineWidth)),
|
|
keypointColor: normalizeColorArray(payload[keyMap.keypointColor], defaults.keypointColor),
|
|
keypointRadius: Math.round(clampNumber(rawKeypointRadius, KEYPOINT_RADIUS_RANGE.min, KEYPOINT_RADIUS_RANGE.max, defaults.keypointRadius))
|
|
};
|
|
if (settings.lineWidth <= 0 && settings.keypointRadius <= 0) {
|
|
settings.lineWidth = defaults.lineWidth;
|
|
settings.keypointRadius = defaults.keypointRadius;
|
|
}
|
|
return settings;
|
|
}
|
|
|
|
function buildPayload(settings) {
|
|
return {
|
|
[RENDER_STYLE_KEYS.version]: settings.version,
|
|
[RENDER_STYLE_KEYS.body.lineWidth]: settings.body.lineWidth,
|
|
[RENDER_STYLE_KEYS.body.keypointColor]: Array.isArray(settings.body.keypointColor) ? settings.body.keypointColor.slice() : null,
|
|
[RENDER_STYLE_KEYS.body.keypointRadius]: settings.body.keypointRadius,
|
|
[RENDER_STYLE_KEYS.hands.lineWidth]: settings.hands.lineWidth,
|
|
[RENDER_STYLE_KEYS.hands.keypointColor]: Array.isArray(settings.hands.keypointColor) ? settings.hands.keypointColor.slice() : null,
|
|
[RENDER_STYLE_KEYS.hands.keypointRadius]: settings.hands.keypointRadius,
|
|
[RENDER_STYLE_KEYS.face.lineWidth]: settings.face.lineWidth,
|
|
[RENDER_STYLE_KEYS.face.keypointColor]: Array.isArray(settings.face.keypointColor) ? settings.face.keypointColor.slice() : null,
|
|
[RENDER_STYLE_KEYS.face.keypointRadius]: settings.face.keypointRadius
|
|
};
|
|
}
|
|
|
|
function buildDefaultSettings(openpose) {
|
|
void openpose;
|
|
return {
|
|
version: RENDER_STYLE_VERSION,
|
|
body: {
|
|
lineWidth: 4,
|
|
keypointColor: null,
|
|
keypointRadius: 4
|
|
},
|
|
hands: {
|
|
lineWidth: 2,
|
|
keypointColor: null,
|
|
keypointRadius: 4
|
|
},
|
|
face: {
|
|
lineWidth: 0,
|
|
keypointColor: [255, 255, 255, 255],
|
|
keypointRadius: 4
|
|
}
|
|
};
|
|
}
|
|
|
|
function buildSettingsFromPayload(payload, defaults) {
|
|
const safePayload = payload && typeof payload === "object" ? payload : {};
|
|
const version = Number(safePayload[RENDER_STYLE_KEYS.version]);
|
|
if (version !== RENDER_STYLE_VERSION) {
|
|
return JSON.parse(JSON.stringify(defaults));
|
|
}
|
|
return {
|
|
version: RENDER_STYLE_VERSION,
|
|
body: normalizeSectionSettings(safePayload, defaults.body, RENDER_STYLE_KEYS.body),
|
|
hands: normalizeSectionSettings(safePayload, defaults.hands, RENDER_STYLE_KEYS.hands),
|
|
face: normalizeSectionSettings(safePayload, defaults.face, RENDER_STYLE_KEYS.face)
|
|
};
|
|
}
|
|
|
|
function ensureVisibleSections(settings, defaults) {
|
|
["body", "hands", "face"].forEach((section) => {
|
|
if (!settings?.[section] || !defaults?.[section]) {
|
|
return;
|
|
}
|
|
if (settings[section].lineWidth <= 0 && settings[section].keypointRadius <= 0) {
|
|
settings[section].lineWidth = defaults[section].lineWidth;
|
|
settings[section].keypointRadius = defaults[section].keypointRadius;
|
|
}
|
|
});
|
|
return settings;
|
|
}
|
|
|
|
function rgbToHex(color) {
|
|
if (!Array.isArray(color) || color.length < 3) {
|
|
return "#000000";
|
|
}
|
|
const toHex = (value) => clampNumber(value, 0, 255, 0).toString(16).padStart(2, "0");
|
|
return `#${toHex(color[0])}${toHex(color[1])}${toHex(color[2])}`;
|
|
}
|
|
|
|
function hexToRgb(hex) {
|
|
if (typeof hex !== "string") {
|
|
return null;
|
|
}
|
|
const value = hex.trim().replace("#", "");
|
|
if (value.length !== 6) {
|
|
return null;
|
|
}
|
|
const r = parseInt(value.slice(0, 2), 16);
|
|
const g = parseInt(value.slice(2, 4), 16);
|
|
const b = parseInt(value.slice(4, 6), 16);
|
|
if (![r, g, b].every((channel) => Number.isFinite(channel))) {
|
|
return null;
|
|
}
|
|
return [r, g, b];
|
|
}
|
|
|
|
const renderStyleState = {
|
|
panel: null,
|
|
openpose: null,
|
|
defaults: null,
|
|
settings: null,
|
|
payload: null,
|
|
controls: null,
|
|
isUpdating: false
|
|
};
|
|
|
|
export function getRenderStylePayload() {
|
|
const defaults = buildDefaultSettings(null);
|
|
const stored = getPersistedJSON(RENDER_STYLE_STORAGE_KEY, null);
|
|
return buildPayload(buildSettingsFromPayload(stored, defaults));
|
|
}
|
|
|
|
export async function syncRenderStyleToServer(payload = getRenderStylePayload()) {
|
|
try {
|
|
const response = await fetch(RENDER_STYLE_SYNC_URL, {
|
|
method: "POST",
|
|
headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify(payload),
|
|
cache: "no-store"
|
|
});
|
|
return response.ok;
|
|
} catch (error) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
function setRenderStyleState(settings) {
|
|
const defaults = renderStyleState.defaults || buildDefaultSettings(null);
|
|
settings = ensureVisibleSections(settings, defaults);
|
|
renderStyleState.settings = settings;
|
|
renderStyleState.payload = buildPayload(settings);
|
|
setPersistedJSON(RENDER_STYLE_STORAGE_KEY, renderStyleState.payload);
|
|
void syncRenderStyleToServer(renderStyleState.payload);
|
|
if (renderStyleState.openpose) {
|
|
renderStyleState.openpose.renderStyleSettings = settings;
|
|
renderStyleState.openpose.renderStylePayload = renderStyleState.payload;
|
|
}
|
|
}
|
|
|
|
function updateColorControls(section, color) {
|
|
const controls = renderStyleState.controls?.[section];
|
|
if (!controls) {
|
|
return;
|
|
}
|
|
const displayColor = Array.isArray(color) ? color : DISPLAY_COLOR_FALLBACKS[section];
|
|
if (controls.colorInput) {
|
|
controls.colorInput.value = rgbToHex(displayColor);
|
|
}
|
|
if (controls.channelInputs) {
|
|
if (controls.channelInputs.r) {
|
|
controls.channelInputs.r.value = displayColor[0];
|
|
}
|
|
if (controls.channelInputs.g) {
|
|
controls.channelInputs.g.value = displayColor[1];
|
|
}
|
|
if (controls.channelInputs.b) {
|
|
controls.channelInputs.b.value = displayColor[2];
|
|
}
|
|
if (controls.channelInputs.a) {
|
|
controls.channelInputs.a.value = displayColor[3];
|
|
}
|
|
}
|
|
}
|
|
|
|
function updateNumberInput(input, value) {
|
|
if (!input) {
|
|
return;
|
|
}
|
|
input.value = `${value}`;
|
|
}
|
|
|
|
function applySettingsToUI(settings) {
|
|
const sections = ["body", "hands", "face"];
|
|
sections.forEach((section) => {
|
|
const controls = renderStyleState.controls?.[section];
|
|
if (!controls) {
|
|
return;
|
|
}
|
|
updateNumberInput(controls.lineWidthInput, settings[section].lineWidth);
|
|
updateNumberInput(controls.keypointRadiusInput, settings[section].keypointRadius);
|
|
updateColorControls(section, settings[section].keypointColor);
|
|
});
|
|
}
|
|
|
|
function buildSectionControls(sectionEl) {
|
|
if (!sectionEl) {
|
|
return null;
|
|
}
|
|
const channelInputs = {
|
|
r: sectionEl.querySelector('[data-render-channel="r"]'),
|
|
g: sectionEl.querySelector('[data-render-channel="g"]'),
|
|
b: sectionEl.querySelector('[data-render-channel="b"]'),
|
|
a: sectionEl.querySelector('[data-render-channel="a"]')
|
|
};
|
|
return {
|
|
lineWidthInput: sectionEl.querySelector('[data-render-field="line_width"]'),
|
|
keypointRadiusInput: sectionEl.querySelector('[data-render-field="keypoint_radius"]'),
|
|
colorInput: sectionEl.querySelector('[data-render-field="color_picker"]'),
|
|
channelInputs
|
|
};
|
|
}
|
|
|
|
function handleNumberChange(section, field, value, inputEl) {
|
|
if (renderStyleState.isUpdating) {
|
|
return;
|
|
}
|
|
const num = Number(value);
|
|
if (!Number.isFinite(num)) {
|
|
return;
|
|
}
|
|
const settings = renderStyleState.settings;
|
|
if (!settings || !settings[section]) {
|
|
return;
|
|
}
|
|
const range = field === "keypointRadius" ? KEYPOINT_RADIUS_RANGE : LINE_WIDTH_RANGE;
|
|
const rounded = Math.round(num);
|
|
const clamped = clampNumber(rounded, range.min, range.max, settings[section][field]);
|
|
renderStyleState.isUpdating = true;
|
|
settings[section][field] = clamped;
|
|
updateNumberInput(inputEl, clamped);
|
|
setRenderStyleState(settings);
|
|
applySettingsToUI(renderStyleState.settings);
|
|
renderStyleState.isUpdating = false;
|
|
}
|
|
|
|
function handleColorChange(section, nextColor) {
|
|
if (renderStyleState.isUpdating) {
|
|
return;
|
|
}
|
|
const settings = renderStyleState.settings;
|
|
if (!settings || !settings[section]) {
|
|
return;
|
|
}
|
|
renderStyleState.isUpdating = true;
|
|
settings[section].keypointColor = normalizeColorArray(nextColor, settings[section].keypointColor);
|
|
updateColorControls(section, settings[section].keypointColor);
|
|
setRenderStyleState(settings);
|
|
renderStyleState.isUpdating = false;
|
|
}
|
|
|
|
function bindSectionHandlers(section, controls) {
|
|
if (!controls) {
|
|
return;
|
|
}
|
|
controls.lineWidthInput?.addEventListener("change", (event) => {
|
|
handleNumberChange(section, "lineWidth", event.target.value, controls.lineWidthInput);
|
|
});
|
|
controls.keypointRadiusInput?.addEventListener("change", (event) => {
|
|
handleNumberChange(section, "keypointRadius", event.target.value, controls.keypointRadiusInput);
|
|
});
|
|
controls.colorInput?.addEventListener("input", (event) => {
|
|
const rgb = hexToRgb(event.target.value);
|
|
if (!rgb) {
|
|
return;
|
|
}
|
|
const current = renderStyleState.settings?.[section]?.keypointColor || DISPLAY_COLOR_FALLBACKS[section] || [0, 0, 0, 255];
|
|
handleColorChange(section, [rgb[0], rgb[1], rgb[2], current[3]]);
|
|
});
|
|
Object.entries(controls.channelInputs || {}).forEach(([channel, input]) => {
|
|
input?.addEventListener("change", (event) => {
|
|
const current = renderStyleState.settings?.[section]?.keypointColor || DISPLAY_COLOR_FALLBACKS[section] || [0, 0, 0, 255];
|
|
const next = current.slice();
|
|
const indexMap = { r: 0, g: 1, b: 2, a: 3 };
|
|
const channelIndex = indexMap[channel];
|
|
const value = Number(event.target.value);
|
|
if (!Number.isFinite(value)) {
|
|
return;
|
|
}
|
|
next[channelIndex] = value;
|
|
handleColorChange(section, next);
|
|
});
|
|
});
|
|
}
|
|
|
|
function setupRenderStylePanel(container, openpose) {
|
|
const panel = container.querySelector(".openpose-overlay.openpose-render-style-overlay");
|
|
renderStyleState.panel = panel;
|
|
renderStyleState.openpose = openpose || null;
|
|
renderStyleState.defaults = buildDefaultSettings(openpose);
|
|
const stored = getPersistedJSON(RENDER_STYLE_STORAGE_KEY, null);
|
|
const settings = buildSettingsFromPayload(stored, renderStyleState.defaults);
|
|
renderStyleState.controls = {
|
|
body: buildSectionControls(panel?.querySelector('[data-render-section="body"]')),
|
|
hands: buildSectionControls(panel?.querySelector('[data-render-section="hands"]')),
|
|
face: buildSectionControls(panel?.querySelector('[data-render-section="face"]'))
|
|
};
|
|
setRenderStyleState(settings);
|
|
renderStyleState.isUpdating = true;
|
|
applySettingsToUI(settings);
|
|
renderStyleState.isUpdating = false;
|
|
Object.entries(renderStyleState.controls || {}).forEach(([section, controls]) => {
|
|
bindSectionHandlers(section, controls);
|
|
});
|
|
|
|
const resetButton = panel?.querySelector('[data-action="render-style-reset"]');
|
|
if (resetButton && !resetButton.dataset.renderStyleResetReady) {
|
|
resetButton.dataset.renderStyleResetReady = "1";
|
|
resetButton.addEventListener("click", () => {
|
|
const defaults = buildDefaultSettings(openpose);
|
|
renderStyleState.defaults = defaults;
|
|
renderStyleState.isUpdating = true;
|
|
applySettingsToUI(defaults);
|
|
renderStyleState.isUpdating = false;
|
|
setRenderStyleState(defaults);
|
|
});
|
|
}
|
|
}
|
|
|
|
export function setupRenderStyleStyles(container) {
|
|
const overlay = container.querySelector(".openpose-overlay.openpose-render-style-overlay");
|
|
// Use the same overlay/card structure and rely on shared overlay styles
|
|
// (no custom max-width/margin/padding here so it matches Guide/About)
|
|
|
|
// Use same background as Pose Editor sidebars for consistency (like Guide/About tabs)
|
|
container.querySelectorAll(".openpose-render-style-overlay .openpose-overlay-card").forEach((card) => {
|
|
card.style.background = "var(--openpose-panel-bg-secondary)";
|
|
card.style.border = "none";
|
|
card.style.borderRadius = "var(--openpose-card-radius)";
|
|
card.style.boxShadow = "0 1px 3px rgba(0,0,0,0.2), 0 1px 2px rgba(0,0,0,0.14)";
|
|
card.style.padding = "16px";
|
|
});
|
|
|
|
const content = container.querySelector(".openpose-render-style-content");
|
|
if (content) {
|
|
content.style.display = "flex";
|
|
content.style.flexDirection = "column";
|
|
content.style.gap = "12px";
|
|
}
|
|
|
|
const header = container.querySelector(".openpose-render-style-header");
|
|
if (header) {
|
|
header.style.display = "flex";
|
|
header.style.alignItems = "center";
|
|
header.style.justifyContent = "space-between";
|
|
header.style.gap = "8px";
|
|
header.style.flexWrap = "wrap";
|
|
}
|
|
|
|
const wrapper = container.querySelector(".openpose-render-style-controls-wrapper");
|
|
if (wrapper) {
|
|
wrapper.style.display = "flex";
|
|
wrapper.style.flexDirection = "column";
|
|
wrapper.style.gap = "12px";
|
|
}
|
|
|
|
const actions = container.querySelector(".openpose-render-style-actions");
|
|
if (actions) {
|
|
actions.style.display = "flex";
|
|
actions.style.gap = "6px";
|
|
actions.style.flexWrap = "wrap";
|
|
actions.style.justifyContent = "flex-end";
|
|
actions.style.marginLeft = "auto";
|
|
}
|
|
|
|
const intro = container.querySelector(".openpose-render-style-intro");
|
|
if (intro) {
|
|
intro.style.fontSize = "12px";
|
|
intro.style.opacity = "0.85";
|
|
intro.style.color = "var(--openpose-text-muted)";
|
|
intro.style.flex = "1 1 auto";
|
|
}
|
|
|
|
container.querySelectorAll(".openpose-render-style-sections").forEach((section) => {
|
|
section.style.display = "flex";
|
|
section.style.flexDirection = "column";
|
|
section.style.gap = "12px";
|
|
});
|
|
|
|
container.querySelectorAll(".openpose-render-style-section").forEach((section) => {
|
|
section.style.display = "flex";
|
|
section.style.flexDirection = "column";
|
|
section.style.gap = "8px";
|
|
section.style.padding = "16px";
|
|
section.style.border = "none";
|
|
section.style.borderRadius = "8px";
|
|
section.style.background = "linear-gradient(rgba(255, 255, 255, 0.035), rgba(255, 255, 255, 0.035)), var(--openpose-panel-bg)";
|
|
section.style.boxSizing = "border-box";
|
|
section.style.boxShadow = "0 1px 2px rgba(0,0,0,0.2)";
|
|
});
|
|
|
|
container.querySelectorAll(".openpose-render-style-section-title").forEach((label) => {
|
|
label.style.fontWeight = "600";
|
|
label.style.fontSize = "13px";
|
|
label.style.color = "var(--openpose-text)";
|
|
});
|
|
|
|
container.querySelectorAll(".openpose-render-style-grid").forEach((grid) => {
|
|
grid.style.display = "grid";
|
|
grid.style.gridTemplateColumns = "140px minmax(0, 1fr)";
|
|
grid.style.gap = "8px 12px";
|
|
grid.style.alignItems = "center";
|
|
});
|
|
|
|
container.querySelectorAll(".openpose-render-style-label").forEach((label) => {
|
|
label.style.color = "var(--openpose-text-muted)";
|
|
label.style.fontFamily = "Arial, sans-serif";
|
|
label.style.fontSize = "12px";
|
|
});
|
|
|
|
container.querySelectorAll(".openpose-render-style-color-row").forEach((row) => {
|
|
row.style.display = "flex";
|
|
row.style.flexWrap = "wrap";
|
|
row.style.alignItems = "center";
|
|
row.style.gap = "6px";
|
|
});
|
|
|
|
container.querySelectorAll(".openpose-render-style-channel").forEach((channel) => {
|
|
channel.style.display = "flex";
|
|
channel.style.alignItems = "center";
|
|
channel.style.gap = "4px";
|
|
});
|
|
|
|
container.querySelectorAll(".openpose-render-style-channel-label").forEach((label) => {
|
|
label.style.fontSize = "11px";
|
|
label.style.color = "var(--openpose-text-muted)";
|
|
});
|
|
|
|
container.querySelectorAll(".openpose-render-style-color").forEach((input) => {
|
|
input.style.width = "40px";
|
|
input.style.minWidth = "40px";
|
|
input.style.height = "28px";
|
|
input.style.padding = "0";
|
|
input.style.border = "1px solid var(--openpose-border)";
|
|
input.style.borderRadius = "4px";
|
|
input.style.background = "var(--openpose-input-bg)";
|
|
});
|
|
|
|
container.querySelectorAll(".openpose-render-style-card .openpose-input").forEach((input) => {
|
|
input.style.padding = "6px 8px";
|
|
input.style.border = "1px solid var(--openpose-border)";
|
|
input.style.borderRadius = "4px";
|
|
input.style.background = "var(--openpose-input-bg)";
|
|
input.style.color = "var(--openpose-input-text)";
|
|
input.style.boxSizing = "border-box";
|
|
input.style.fontFamily = "Arial, sans-serif";
|
|
});
|
|
|
|
container.querySelectorAll(".openpose-render-style-number").forEach((input) => {
|
|
input.style.maxWidth = "100px";
|
|
});
|
|
|
|
container.querySelectorAll(".openpose-render-style-input-row").forEach((row) => {
|
|
row.style.display = "flex";
|
|
row.style.alignItems = "center";
|
|
row.style.gap = "6px";
|
|
});
|
|
|
|
container.querySelectorAll(".openpose-render-style-unit").forEach((unit) => {
|
|
unit.style.fontSize = "11px";
|
|
unit.style.color = "var(--openpose-text-muted)";
|
|
unit.style.fontWeight = "500";
|
|
unit.style.textTransform = "uppercase";
|
|
unit.style.letterSpacing = "0.5px";
|
|
});
|
|
|
|
container.querySelectorAll(".openpose-render-style-channel-input").forEach((input) => {
|
|
input.style.width = "64px";
|
|
});
|
|
|
|
// Warning styling now handled by global openpose-alert CSS
|
|
|
|
applySidebarButtonStyles(container.querySelectorAll(".openpose-render-style-card .openpose-btn"));
|
|
}
|
|
|
|
registerModule({
|
|
id: "render-style",
|
|
labelKey: "render_style.summary.title",
|
|
order: 25,
|
|
slot: "overlay",
|
|
buildUI: buildRenderStylePanelHtml,
|
|
initUI: (container, openpose) => {
|
|
setupRenderStyleStyles(container);
|
|
setupRenderStylePanel(container, openpose);
|
|
},
|
|
onActivate: ({ openpose }) => {
|
|
if (!openpose) {
|
|
return;
|
|
}
|
|
if (!renderStyleState.panel && openpose.container) {
|
|
renderStyleState.panel = openpose.container.querySelector(".openpose-overlay.openpose-render-style-overlay");
|
|
}
|
|
openpose.setCanvasAreaVisible(true);
|
|
openpose.setSidebarsVisible(false);
|
|
openpose.setOverlayPlaceholderWidths(false);
|
|
openpose.setSidebarControlsDisabled(true);
|
|
openpose.setBackgroundControlsEnabled(false);
|
|
},
|
|
summary: {
|
|
icon: UiIcons.svg('sliders', { size: 14, className: 'openpose-sidebar-icon' }),
|
|
titleKey: "render_style.summary.title",
|
|
descriptionKey: "render_style.summary.description"
|
|
}
|
|
});
|