Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7c6bd49225 | ||
|
|
683b278e07 | ||
|
|
75713799d9 | ||
|
|
714718e819 | ||
|
|
342663a9b1 | ||
|
|
95b70a9e5f | ||
|
|
0a4b756d50 | ||
|
|
602e7a796b | ||
|
|
9c70240745 | ||
|
|
f63fcd8c3c | ||
|
|
b2655a1fc5 | ||
|
|
7707b9a22b | ||
|
|
a12fde32ac | ||
|
|
c3402dc59b | ||
|
|
d41ada5d30 |
@@ -0,0 +1,25 @@
|
||||
name: Publish to Comfy registry
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- "pyproject.toml"
|
||||
|
||||
permissions:
|
||||
issues: write
|
||||
|
||||
jobs:
|
||||
publish-node:
|
||||
name: Publish Custom Node to registry
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ github.repository_owner == 'Light-x02' }}
|
||||
steps:
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v4
|
||||
- name: Publish Custom Node
|
||||
uses: Comfy-Org/publish-node-action@v1
|
||||
with:
|
||||
## Add your own personal access token to your Github Repository secrets and reference it here.
|
||||
personal_access_token: ${{ secrets.REGISTRY_ACCESS_TOKEN }}
|
||||
@@ -1,5 +0,0 @@
|
||||
# Changelog
|
||||
|
||||
## [1.0.0] - 2024-12-23
|
||||
### Added
|
||||
- Initial release of the **ComfyUI-FluxSettingsNode**.
|
||||
+92
-104
@@ -1,104 +1,92 @@
|
||||
import comfy.samplers
|
||||
import comfy.sample
|
||||
from comfy.k_diffusion import sampling as k_diffusion_sampling
|
||||
import latent_preview
|
||||
import torch
|
||||
import comfy.utils
|
||||
import node_helpers
|
||||
from comfy.comfy_types import IO, ComfyNodeABC, InputTypeDict
|
||||
|
||||
class Noise_EmptyNoise:
|
||||
def __init__(self):
|
||||
self.seed = 0
|
||||
|
||||
def generate_noise(self, input_latent):
|
||||
latent_image = input_latent["samples"]
|
||||
return torch.zeros(latent_image.shape, dtype=latent_image.dtype, layout=latent_image.layout, device="cpu")
|
||||
|
||||
class Noise_RandomNoise:
|
||||
def __init__(self, seed):
|
||||
self.seed = seed
|
||||
|
||||
def generate_noise(self, input_latent):
|
||||
latent_image = input_latent["samples"]
|
||||
batch_inds = input_latent["batch_index"] if "batch_index" in input_latent else None
|
||||
return comfy.sample.prepare_noise(latent_image, self.seed, batch_inds)
|
||||
|
||||
class DisableNoise:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {"required": {}}
|
||||
|
||||
RETURN_TYPES = ("NOISE",)
|
||||
FUNCTION = "get_noise"
|
||||
CATEGORY = "sampling/custom_sampling/noise"
|
||||
|
||||
def get_noise(self):
|
||||
return (Noise_EmptyNoise(),)
|
||||
|
||||
class FluxSettingsNode(ComfyNodeABC, DisableNoise):
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {
|
||||
"required": {
|
||||
"model": ("MODEL", {"pos": (0, 50)}), # Adjusted position for model
|
||||
"conditioning": ("CONDITIONING", {"pos": (200, 50)}), # Adjusted position for conditioning
|
||||
"guidance": ("FLOAT", {"default": 3.5, "min": 0.0, "max": 100.0, "step": 0.1}),
|
||||
"sampler_name": (comfy.samplers.SAMPLER_NAMES, ),
|
||||
"scheduler": (comfy.samplers.SCHEDULER_NAMES, ),
|
||||
"steps": ("INT", {"default": 20, "min": 1, "max": 10000}),
|
||||
"denoise": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.01}),
|
||||
"noise_seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("CONDITIONING", "SAMPLER", "SIGMAS", "NOISE")
|
||||
|
||||
CATEGORY = "sampling/custom_sampling"
|
||||
|
||||
FUNCTION = "execute"
|
||||
|
||||
def apply_guidance(self, conditioning, guidance):
|
||||
c = node_helpers.conditioning_set_values(conditioning, {"guidance": guidance})
|
||||
return (c, )
|
||||
|
||||
def get_sampler(self, sampler_name):
|
||||
sampler = comfy.samplers.sampler_object(sampler_name)
|
||||
return (sampler, )
|
||||
|
||||
def get_sigmas(self, model, scheduler, steps, denoise):
|
||||
total_steps = steps
|
||||
if denoise < 1.0:
|
||||
if denoise <= 0.0:
|
||||
return (torch.FloatTensor([]),)
|
||||
total_steps = int(steps / denoise)
|
||||
|
||||
sigmas = comfy.samplers.calculate_sigmas(
|
||||
model.get_model_object("model_sampling"),
|
||||
scheduler,
|
||||
total_steps
|
||||
).cpu()
|
||||
|
||||
sigmas = sigmas[-(steps + 1):]
|
||||
return (sigmas, )
|
||||
|
||||
def get_noise(self, noise_seed):
|
||||
return (Noise_RandomNoise(noise_seed),)
|
||||
|
||||
def execute(self, model, conditioning, guidance, sampler_name, scheduler, steps, denoise, noise_seed):
|
||||
c, = self.apply_guidance(conditioning, guidance)
|
||||
sampler, = self.get_sampler(sampler_name)
|
||||
sigmas, = self.get_sigmas(model, scheduler, steps, denoise)
|
||||
noise, = self.get_noise(noise_seed)
|
||||
|
||||
return (c, sampler, sigmas, noise)
|
||||
|
||||
NODE_CLASS_MAPPINGS = {
|
||||
"FluxSettingsNode": FluxSettingsNode,
|
||||
"DisableNoise": DisableNoise
|
||||
}
|
||||
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"FluxSettingsNode": "Flux Settings Node",
|
||||
"DisableNoise": "Disable Noise",
|
||||
}
|
||||
import comfy.samplers
|
||||
import comfy.sample
|
||||
import torch
|
||||
import comfy.utils
|
||||
import node_helpers
|
||||
from comfy.comfy_types import ComfyNodeABC
|
||||
|
||||
class Noise_EmptyNoise:
|
||||
def __init__(self):
|
||||
self.seed = 0
|
||||
|
||||
def generate_noise(self, input_latent):
|
||||
latent_image = input_latent["samples"]
|
||||
return torch.zeros(latent_image.shape, dtype=latent_image.dtype, layout=latent_image.layout, device=latent_image.device)
|
||||
|
||||
class Noise_RandomNoise:
|
||||
def __init__(self, seed):
|
||||
self.seed = seed
|
||||
|
||||
def generate_noise(self, input_latent):
|
||||
latent_image = input_latent["samples"]
|
||||
batch_inds = input_latent.get("batch_index", None)
|
||||
return comfy.sample.prepare_noise(latent_image, self.seed, batch_inds)
|
||||
|
||||
class DisableNoise:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {"required": {}}
|
||||
|
||||
RETURN_TYPES = ("NOISE",)
|
||||
FUNCTION = "get_noise"
|
||||
CATEGORY = "sampling/custom_sampling/noise"
|
||||
|
||||
def get_noise(self):
|
||||
return (Noise_EmptyNoise(),)
|
||||
|
||||
class FluxSettingsNode(ComfyNodeABC):
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {
|
||||
"required": {
|
||||
"model": ("MODEL", {"pos": (0, 50)}),
|
||||
"conditioning": ("CONDITIONING", {"pos": (200, 50)}),
|
||||
"guidance": ("FLOAT", {"default": 3.5, "min": 0.0, "max": 100.0, "step": 0.1}),
|
||||
"sampler_name": (comfy.samplers.SAMPLER_NAMES, {"help": "Choose a sampling method"}),
|
||||
"scheduler": (comfy.samplers.SCHEDULER_NAMES, {"help": "Choose a scheduler"}),
|
||||
"steps": ("INT", {"default": 20, "min": 1, "max": 10000}),
|
||||
"denoise": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.01}),
|
||||
"noise_seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("CONDITIONING", "SAMPLER", "SIGMAS", "NOISE")
|
||||
CATEGORY = "sampling/custom_sampling"
|
||||
FUNCTION = "execute"
|
||||
|
||||
def apply_guidance(self, conditioning, guidance):
|
||||
return (node_helpers.conditioning_set_values(conditioning, {"guidance": guidance}),)
|
||||
|
||||
def get_sampler(self, sampler_name):
|
||||
if sampler_name not in comfy.samplers.SAMPLER_NAMES:
|
||||
raise ValueError(f"Invalid sampler name: {sampler_name}")
|
||||
return comfy.samplers.sampler_object(sampler_name),
|
||||
|
||||
def get_sigmas(self, model, scheduler, steps, denoise):
|
||||
total_steps = int(steps / denoise) if denoise < 1.0 else steps
|
||||
sigmas = comfy.samplers.calculate_sigmas(
|
||||
model.get_model_object("model_sampling"),
|
||||
scheduler,
|
||||
total_steps
|
||||
).cpu()[-(steps + 1):]
|
||||
return (sigmas,)
|
||||
|
||||
def get_noise(self, noise_seed):
|
||||
return (Noise_RandomNoise(noise_seed),)
|
||||
|
||||
def execute(self, model, conditioning, guidance, sampler_name, scheduler, steps, denoise, noise_seed):
|
||||
c = self.apply_guidance(conditioning, guidance)[0]
|
||||
sampler = self.get_sampler(sampler_name)[0]
|
||||
sigmas = self.get_sigmas(model, scheduler, steps, denoise)[0]
|
||||
noise = self.get_noise(noise_seed)[0]
|
||||
return c, sampler, sigmas, noise
|
||||
|
||||
NODE_CLASS_MAPPINGS = {
|
||||
"FluxSettingsNode": FluxSettingsNode,
|
||||
"DisableNoise": DisableNoise
|
||||
}
|
||||
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"FluxSettingsNode": "Flux Settings Node",
|
||||
"DisableNoise": "Disable Noise",
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# FluxSettingsNode
|
||||
|
||||
**Version**: 1.1.0
|
||||
**Version**: 1.1.1
|
||||
**Author**: [Light-x02](https://github.com/Light-x02)
|
||||
|
||||
## Support My Work
|
||||
@@ -25,7 +25,6 @@ FluxSettingsNode is fully compatible with the extension [ComfyUI-ImageMetadataEx
|
||||
## Installation
|
||||
|
||||
### Method 1: Install via ComfyUI Manager
|
||||
*Coming Soon...*
|
||||
|
||||
2. Go to the **Custom Nodes** section in the interface.
|
||||
3. Search for **"FluxSettingsNode"** and install it directly from the ComfyUI Manager.
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
[project]
|
||||
name = "comfyui-fluxsettingsnode"
|
||||
description = "A combined node for ComfyUI with six configurable tabs for managing multiple parameter configurations, including guidance, sampler, scheduler, steps, and denoise."
|
||||
version = "1.1.1"
|
||||
license = {file = "LICENSE"}
|
||||
|
||||
[project.urls]
|
||||
Repository = "https://github.com/Light-x02/ComfyUI-FluxSettingsNode"
|
||||
# Used by Comfy Registry https://comfyregistry.org
|
||||
|
||||
[tool.comfy]
|
||||
PublisherId = "lightx02"
|
||||
DisplayName = "ComfyUI-FluxSettingsNode"
|
||||
Icon = ""
|
||||
+71
-108
@@ -16,6 +16,37 @@ const TAB_CONFIG = {
|
||||
yPosition: -5
|
||||
};
|
||||
|
||||
const DEFAULT_TAB_CONTENT = {
|
||||
guidance: 3.5,
|
||||
sampler_name: "euler",
|
||||
scheduler: "normal",
|
||||
steps: 20,
|
||||
denoise: 1.0
|
||||
};
|
||||
|
||||
function saveTabContent(context, tabIndex) {
|
||||
context.tabContents[tabIndex] = {
|
||||
guidance: context.guidanceWidget?.value ?? DEFAULT_TAB_CONTENT.guidance,
|
||||
sampler_name: context.samplerWidget?.value ?? DEFAULT_TAB_CONTENT.sampler_name,
|
||||
scheduler: context.schedulerWidget?.value ?? DEFAULT_TAB_CONTENT.scheduler,
|
||||
steps: context.stepsWidget?.value ?? DEFAULT_TAB_CONTENT.steps,
|
||||
denoise: context.denoiseWidget?.value ?? DEFAULT_TAB_CONTENT.denoise
|
||||
};
|
||||
}
|
||||
|
||||
function loadTabContent(context, tabIndex) {
|
||||
const content = context.tabContents[tabIndex] || {};
|
||||
context.guidanceWidget.value = content.guidance ?? DEFAULT_TAB_CONTENT.guidance;
|
||||
context.samplerWidget.value = content.sampler_name ?? DEFAULT_TAB_CONTENT.sampler_name;
|
||||
context.schedulerWidget.value = content.scheduler ?? DEFAULT_TAB_CONTENT.scheduler;
|
||||
context.stepsWidget.value = content.steps ?? DEFAULT_TAB_CONTENT.steps;
|
||||
context.denoiseWidget.value = content.denoise ?? DEFAULT_TAB_CONTENT.denoise;
|
||||
}
|
||||
|
||||
function initializeWidgets(node, widgetNames) {
|
||||
return widgetNames.map((name, index) => node.widgets[index] || { value: null, callback: null });
|
||||
}
|
||||
|
||||
app.registerExtension({
|
||||
name: "FluxSettingsNode",
|
||||
|
||||
@@ -30,57 +61,31 @@ app.registerExtension({
|
||||
const originalOnConfigure = nodeType.prototype.onConfigure;
|
||||
|
||||
nodeType.prototype.onNodeCreated = function () {
|
||||
if (originalOnNodeCreated) {
|
||||
originalOnNodeCreated.apply(this, arguments);
|
||||
}
|
||||
if (originalOnNodeCreated) originalOnNodeCreated.apply(this, arguments);
|
||||
|
||||
// Ensure widgets are defined
|
||||
const widgetDefaults = { value: null, callback: null };
|
||||
this.guidanceWidget = this.widgets[0] || widgetDefaults; // guidance corresponds to widget[0]
|
||||
this.samplerWidget = this.widgets[1] || widgetDefaults; // sampler_name corresponds to widget[1]
|
||||
this.schedulerWidget = this.widgets[2] || widgetDefaults; // scheduler corresponds to widget[2]
|
||||
this.stepsWidget = this.widgets[3] || widgetDefaults; // steps corresponds to widget[3]
|
||||
this.denoiseWidget = this.widgets[4] || widgetDefaults; // denoise corresponds to widget[4]
|
||||
// Initialize widgets
|
||||
[this.guidanceWidget, this.samplerWidget, this.schedulerWidget, this.stepsWidget, this.denoiseWidget] = initializeWidgets(this, [
|
||||
"guidance",
|
||||
"sampler_name",
|
||||
"scheduler",
|
||||
"steps",
|
||||
"denoise"
|
||||
]);
|
||||
|
||||
// Initialize tab state and content
|
||||
this.activeTab = 0;
|
||||
this.tabContents = TAB_CONFIG.labels.map(() => ({
|
||||
guidance: 3.5,
|
||||
sampler_name: "euler",
|
||||
scheduler: "normal",
|
||||
steps: 20,
|
||||
denoise: 1.0
|
||||
}));
|
||||
this.tabContents = TAB_CONFIG.labels.map(() => ({ ...DEFAULT_TAB_CONTENT }));
|
||||
|
||||
// Add change listeners to widgets
|
||||
this.guidanceWidget.callback = () => {
|
||||
console.log(`Tab ${this.activeTab}: Saving guidance =`, this.guidanceWidget.value);
|
||||
this.tabContents[this.activeTab].guidance = this.guidanceWidget.value;
|
||||
};
|
||||
|
||||
this.samplerWidget.callback = () => {
|
||||
console.log(`Tab ${this.activeTab}: Saving sampler_name =`, this.samplerWidget.value);
|
||||
this.tabContents[this.activeTab].sampler_name = this.samplerWidget.value;
|
||||
};
|
||||
|
||||
this.schedulerWidget.callback = () => {
|
||||
this.tabContents[this.activeTab].scheduler = this.schedulerWidget.value;
|
||||
};
|
||||
|
||||
this.stepsWidget.callback = () => {
|
||||
this.tabContents[this.activeTab].steps = this.stepsWidget.value;
|
||||
};
|
||||
|
||||
this.denoiseWidget.callback = () => {
|
||||
this.tabContents[this.activeTab].denoise = this.denoiseWidget.value;
|
||||
};
|
||||
// Define widget callbacks
|
||||
this.guidanceWidget.callback = () => saveTabContent(this, this.activeTab);
|
||||
this.samplerWidget.callback = () => saveTabContent(this, this.activeTab);
|
||||
this.schedulerWidget.callback = () => saveTabContent(this, this.activeTab);
|
||||
this.stepsWidget.callback = () => saveTabContent(this, this.activeTab);
|
||||
this.denoiseWidget.callback = () => saveTabContent(this, this.activeTab);
|
||||
};
|
||||
|
||||
nodeType.prototype.onDrawForeground = function (ctx) {
|
||||
if (originalOnDrawForeground) {
|
||||
originalOnDrawForeground.apply(this, arguments);
|
||||
}
|
||||
|
||||
if (originalOnDrawForeground) originalOnDrawForeground.apply(this, arguments);
|
||||
if (this.flags.collapsed) return;
|
||||
|
||||
ctx.save();
|
||||
@@ -111,90 +116,48 @@ app.registerExtension({
|
||||
const [x, y] = local_pos;
|
||||
const { yPosition, height, width, spacing, offset, labels } = TAB_CONFIG;
|
||||
|
||||
// Check if the click is outside the tab area
|
||||
if (y < yPosition || y > yPosition + height) {
|
||||
return false; // Allow other events like zoom or pan
|
||||
}
|
||||
if (y < yPosition || y > yPosition + height) return false;
|
||||
|
||||
for (let i = 0; i < labels.length; i++) {
|
||||
const tabX = offset + (width + spacing) * i;
|
||||
if (x >= tabX && x <= tabX + width) {
|
||||
if (i === this.activeTab) return false;
|
||||
const clickedTabIndex = Math.floor((x - offset) / (width + spacing));
|
||||
if (
|
||||
clickedTabIndex < 0 ||
|
||||
clickedTabIndex >= labels.length ||
|
||||
clickedTabIndex === this.activeTab
|
||||
)
|
||||
return false;
|
||||
|
||||
console.log(`Before switching tabs - Tab ${this.activeTab} content:`, this.tabContents[this.activeTab]);
|
||||
console.log(`Before switching tabs - Tab ${this.activeTab} content:`, this.tabContents[this.activeTab]);
|
||||
|
||||
// Save current widget values to current tab
|
||||
this.tabContents[this.activeTab] = {
|
||||
guidance: this.guidanceWidget?.value ?? 3.5,
|
||||
sampler_name: this.samplerWidget?.value ?? "euler",
|
||||
scheduler: this.schedulerWidget?.value ?? "normal",
|
||||
steps: this.stepsWidget?.value ?? 20,
|
||||
denoise: this.denoiseWidget?.value ?? 1.0
|
||||
};
|
||||
saveTabContent(this, this.activeTab);
|
||||
this.activeTab = clickedTabIndex;
|
||||
console.log(`Loading values for Tab ${this.activeTab}:`, this.tabContents[this.activeTab]);
|
||||
loadTabContent(this, this.activeTab);
|
||||
|
||||
console.log(`Saved values for Tab ${this.activeTab}:`, this.tabContents[this.activeTab]);
|
||||
|
||||
// Switch tab
|
||||
this.activeTab = i;
|
||||
|
||||
// Load content from new tab
|
||||
const newTabContent = this.tabContents[i] || {};
|
||||
console.log(`Loading values for Tab ${i}:`, newTabContent);
|
||||
this.guidanceWidget.value = newTabContent.guidance ?? 3.5;
|
||||
this.samplerWidget.value = newTabContent.sampler_name ?? "euler";
|
||||
this.schedulerWidget.value = newTabContent.scheduler ?? "normal";
|
||||
this.stepsWidget.value = newTabContent.steps ?? 20;
|
||||
this.denoiseWidget.value = newTabContent.denoise ?? 1.0;
|
||||
|
||||
this.setDirtyCanvas(true);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
this.setDirtyCanvas(true);
|
||||
return true;
|
||||
};
|
||||
|
||||
nodeType.prototype.getBounding = function () {
|
||||
const bounds = originalGetBounding ? originalGetBounding.apply(this, arguments) : [0, 0, 200, 300];
|
||||
const tabsHeight = Math.abs(TAB_CONFIG.yPosition) + TAB_CONFIG.height;
|
||||
bounds[1] -= tabsHeight; // Extend top boundary to include tabs
|
||||
bounds[3] += 100; // Drastically increase height to force alignment
|
||||
bounds[1] -= tabsHeight;
|
||||
bounds[3] += 100;
|
||||
return bounds;
|
||||
};
|
||||
|
||||
nodeType.prototype.onSerialize = function (o) {
|
||||
if (originalOnSerialize) {
|
||||
originalOnSerialize.apply(this, arguments);
|
||||
}
|
||||
if (originalOnSerialize) originalOnSerialize.apply(this, arguments);
|
||||
o.tabContents = this.tabContents;
|
||||
o.activeTab = this.activeTab;
|
||||
};
|
||||
|
||||
nodeType.prototype.onConfigure = function (o) {
|
||||
if (originalOnConfigure) {
|
||||
originalOnConfigure.apply(this, arguments);
|
||||
}
|
||||
if (o.tabContents && Array.isArray(o.tabContents)) {
|
||||
this.tabContents = TAB_CONFIG.labels.map((_, i) =>
|
||||
o.tabContents[i] || {
|
||||
guidance: 3.5,
|
||||
sampler_name: "euler",
|
||||
scheduler: "normal",
|
||||
steps: 20,
|
||||
denoise: 1.0
|
||||
}
|
||||
);
|
||||
this.activeTab = o.activeTab >= 0 && o.activeTab < TAB_CONFIG.labels.length ? o.activeTab : 0;
|
||||
if (originalOnConfigure) originalOnConfigure.apply(this, arguments);
|
||||
|
||||
const currentTabContent = this.tabContents[this.activeTab];
|
||||
console.log(`Restoring Tab ${this.activeTab} Content:`, currentTabContent);
|
||||
if (this.guidanceWidget && this.samplerWidget && this.schedulerWidget && this.stepsWidget && this.denoiseWidget) {
|
||||
this.guidanceWidget.value = currentTabContent.guidance ?? 3.5;
|
||||
this.samplerWidget.value = currentTabContent.sampler_name ?? "euler";
|
||||
this.schedulerWidget.value = currentTabContent.scheduler ?? "normal";
|
||||
this.stepsWidget.value = currentTabContent.steps ?? 20;
|
||||
this.denoiseWidget.value = currentTabContent.denoise ?? 1.0;
|
||||
}
|
||||
if (o.tabContents && Array.isArray(o.tabContents)) {
|
||||
this.tabContents = TAB_CONFIG.labels.map((_, i) => o.tabContents[i] || { ...DEFAULT_TAB_CONTENT });
|
||||
this.activeTab = Math.min(Math.max(o.activeTab || 0, 0), TAB_CONFIG.labels.length - 1);
|
||||
loadTabContent(this, this.activeTab);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user