Compare commits
4 Commits
main
...
0687ee651e
| Author | SHA1 | Date | |
|---|---|---|---|
| 0687ee651e | |||
| 8b8cb29575 | |||
| 4243f703e6 | |||
| bb2952afcf |
@@ -293,15 +293,72 @@ class ProjectKey:
|
|||||||
return (str(val),)
|
return (str(val),)
|
||||||
|
|
||||||
|
|
||||||
|
class ProjectResolution:
|
||||||
|
"""Fetches a (width, height) pair from a resolution series by loop index."""
|
||||||
|
@classmethod
|
||||||
|
def INPUT_TYPES(s):
|
||||||
|
return {
|
||||||
|
"required": {
|
||||||
|
"source_label": ("STRING", {"default": "", "multiline": False}),
|
||||||
|
"key_name": ("STRING", {"default": "resolutions", "multiline": False}),
|
||||||
|
"index": ("INT", {"default": 0, "min": 0, "max": 9999}),
|
||||||
|
},
|
||||||
|
"optional": {
|
||||||
|
"manager_url": ("STRING", {"default": "http://localhost:8080", "multiline": False}),
|
||||||
|
"project_name": ("STRING", {"default": "", "multiline": False}),
|
||||||
|
"file_name": ("STRING", {"default": "", "multiline": False}),
|
||||||
|
"sequence_number": ("INT", {"default": 1, "min": 1, "max": 9999}),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
RETURN_TYPES = ("INT", "INT")
|
||||||
|
RETURN_NAMES = ("width", "height")
|
||||||
|
FUNCTION = "fetch_resolution"
|
||||||
|
CATEGORY = "utils/json/project"
|
||||||
|
OUTPUT_NODE = False
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def IS_CHANGED(cls, **kwargs):
|
||||||
|
return float("nan")
|
||||||
|
|
||||||
|
def fetch_resolution(self, source_label, key_name, index,
|
||||||
|
manager_url="http://localhost:8080", project_name="",
|
||||||
|
file_name="", sequence_number=1):
|
||||||
|
sequence_number = int(sequence_number)
|
||||||
|
logger.info("ProjectResolution.fetch_resolution: source=%s key=%s url=%s project=%s file=%s seq=%s index=%s",
|
||||||
|
source_label, key_name, manager_url, project_name, file_name, sequence_number, index)
|
||||||
|
# source_label is used by JS to identify which ProjectSource to sync
|
||||||
|
# config from. The actual config arrives via the optional widgets below.
|
||||||
|
data = _fetch_data(manager_url, project_name, file_name, sequence_number)
|
||||||
|
if data.get("error") in ("http_error", "network_error", "parse_error"):
|
||||||
|
logger.warning("ProjectResolution.fetch_resolution failed: %s", data.get("message"))
|
||||||
|
return (512, 512)
|
||||||
|
|
||||||
|
series = data.get(key_name)
|
||||||
|
if not isinstance(series, list) or len(series) == 0:
|
||||||
|
logger.warning("ProjectResolution: key '%s' is not a resolution series", key_name)
|
||||||
|
return (512, 512)
|
||||||
|
|
||||||
|
clamped = max(0, min(index, len(series) - 1))
|
||||||
|
entry = series[clamped]
|
||||||
|
if not isinstance(entry, (list, tuple)) or len(entry) < 2:
|
||||||
|
logger.warning("ProjectResolution: entry at index %d is malformed: %r", clamped, entry)
|
||||||
|
return (512, 512)
|
||||||
|
|
||||||
|
return (to_int(entry[0]), to_int(entry[1]))
|
||||||
|
|
||||||
|
|
||||||
# --- Mappings ---
|
# --- Mappings ---
|
||||||
PROJECT_NODE_CLASS_MAPPINGS = {
|
PROJECT_NODE_CLASS_MAPPINGS = {
|
||||||
"ProjectLoaderDynamic": ProjectLoaderDynamic,
|
"ProjectLoaderDynamic": ProjectLoaderDynamic,
|
||||||
"ProjectSource": ProjectSource,
|
"ProjectSource": ProjectSource,
|
||||||
"ProjectKey": ProjectKey,
|
"ProjectKey": ProjectKey,
|
||||||
|
"ProjectResolution": ProjectResolution,
|
||||||
}
|
}
|
||||||
|
|
||||||
PROJECT_NODE_DISPLAY_NAME_MAPPINGS = {
|
PROJECT_NODE_DISPLAY_NAME_MAPPINGS = {
|
||||||
"ProjectLoaderDynamic": "Project Loader (Dynamic)",
|
"ProjectLoaderDynamic": "Project Loader (Dynamic)",
|
||||||
"ProjectSource": "Project Source",
|
"ProjectSource": "Project Source",
|
||||||
"ProjectKey": "Project Key",
|
"ProjectKey": "Project Key",
|
||||||
|
"ProjectResolution": "Project Resolution",
|
||||||
}
|
}
|
||||||
|
|||||||
+72
-1
@@ -411,6 +411,17 @@ def render_batch_processor(state: AppState):
|
|||||||
# Single sequence card
|
# Single sequence card
|
||||||
# ======================================================================
|
# ======================================================================
|
||||||
|
|
||||||
|
def _is_resolution_series(val) -> bool:
|
||||||
|
"""Return True if val is a non-empty list of [width, height] numeric pairs."""
|
||||||
|
if not isinstance(val, list) or len(val) == 0:
|
||||||
|
return False
|
||||||
|
return all(
|
||||||
|
isinstance(entry, (list, tuple)) and len(entry) == 2
|
||||||
|
and all(isinstance(v, (int, float)) for v in entry)
|
||||||
|
for entry in val
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def _render_sequence_card(i, seq, batch_list, data, file_path, state,
|
def _render_sequence_card(i, seq, batch_list, data, file_path, state,
|
||||||
src_cache, src_seq_select, standard_keys,
|
src_cache, src_seq_select, standard_keys,
|
||||||
refresh_list):
|
refresh_list):
|
||||||
@@ -533,6 +544,66 @@ def _render_sequence_card(i, seq, batch_list, data, file_path, state,
|
|||||||
dict_textarea('Specific Negative', seq, 'negative').classes(
|
dict_textarea('Specific Negative', seq, 'negative').classes(
|
||||||
'w-full q-mt-sm').props('outlined rows=2')
|
'w-full q-mt-sm').props('outlined rows=2')
|
||||||
|
|
||||||
|
# --- Resolution Series ---
|
||||||
|
res_keys = [k for k, v in seq.items() if _is_resolution_series(v)]
|
||||||
|
if res_keys:
|
||||||
|
ui.label('Resolution Series').classes('text-caption text-weight-bold q-mt-md')
|
||||||
|
for res_key in res_keys:
|
||||||
|
series: list = seq[res_key]
|
||||||
|
with ui.card().classes('w-full q-pa-sm q-mt-xs').props('flat bordered'):
|
||||||
|
with ui.row().classes('items-center q-mb-xs'):
|
||||||
|
ui.label(res_key).classes('text-caption col')
|
||||||
|
def del_series(k=res_key):
|
||||||
|
del seq[k]
|
||||||
|
commit()
|
||||||
|
ui.button(icon='delete', on_click=del_series).props(
|
||||||
|
'flat dense round size=xs color=negative')
|
||||||
|
with ui.row().classes('text-caption text-grey q-mb-xs'):
|
||||||
|
ui.label('#').style('width:24px')
|
||||||
|
ui.label('Width').classes('col')
|
||||||
|
ui.label('Height').classes('col')
|
||||||
|
ui.label('').style('width:28px')
|
||||||
|
for idx, entry in enumerate(series):
|
||||||
|
with ui.row().classes('items-center w-full'):
|
||||||
|
ui.label(str(idx + 1)).classes('text-caption').style('width:24px')
|
||||||
|
w_inp = ui.number(value=int(entry[0]), min=1, step=1).classes(
|
||||||
|
'col').props('outlined dense hide-bottom-space')
|
||||||
|
h_inp = ui.number(value=int(entry[1]), min=1, step=1).classes(
|
||||||
|
'col').props('outlined dense hide-bottom-space')
|
||||||
|
|
||||||
|
def _sync_wh(i=idx, k=res_key, wi=w_inp, hi=h_inp):
|
||||||
|
seq[k][i] = [
|
||||||
|
int(wi.value) if wi.value else 512,
|
||||||
|
int(hi.value) if hi.value else 512,
|
||||||
|
]
|
||||||
|
commit()
|
||||||
|
|
||||||
|
w_inp.on('blur', lambda _, s=_sync_wh: s())
|
||||||
|
h_inp.on('blur', lambda _, s=_sync_wh: s())
|
||||||
|
|
||||||
|
def del_row(i=idx, k=res_key):
|
||||||
|
if i < len(seq.get(k, [])):
|
||||||
|
seq[k].pop(i)
|
||||||
|
commit()
|
||||||
|
ui.button(icon='remove', on_click=del_row).props(
|
||||||
|
'flat dense round size=xs')
|
||||||
|
|
||||||
|
def add_row(k=res_key):
|
||||||
|
seq[k].append([512, 512])
|
||||||
|
commit()
|
||||||
|
ui.button('+ Add row', icon='add', on_click=add_row).props(
|
||||||
|
'flat dense size=sm').classes('q-mt-xs')
|
||||||
|
|
||||||
|
with ui.expansion('Add Resolution Series', icon='straighten').classes('w-full q-mt-sm'):
|
||||||
|
new_res_key = ui.input('Key name', value='resolutions').props('outlined dense')
|
||||||
|
def add_res_series():
|
||||||
|
k = new_res_key.value.strip()
|
||||||
|
if k and k not in seq:
|
||||||
|
seq[k] = [[512, 512], [1024, 1024]]
|
||||||
|
new_res_key.set_value('')
|
||||||
|
commit()
|
||||||
|
ui.button('Add', icon='add', on_click=add_res_series).props('outlined dense')
|
||||||
|
|
||||||
with splitter.after:
|
with splitter.after:
|
||||||
# Mode
|
# Mode
|
||||||
dict_number('Mode', seq, 'mode').props('outlined').classes('w-full')
|
dict_number('Mode', seq, 'mode').props('outlined').classes('w-full')
|
||||||
@@ -626,7 +697,7 @@ def _render_sequence_card(i, seq, batch_list, data, file_path, state,
|
|||||||
# --- Custom Parameters ---
|
# --- Custom Parameters ---
|
||||||
ui.label('Custom Parameters').classes('section-header q-mt-md')
|
ui.label('Custom Parameters').classes('section-header q-mt-md')
|
||||||
|
|
||||||
custom_keys = [k for k in seq.keys() if k not in standard_keys]
|
custom_keys = [k for k in seq.keys() if k not in standard_keys and not _is_resolution_series(seq.get(k))]
|
||||||
if custom_keys:
|
if custom_keys:
|
||||||
for k in custom_keys:
|
for k in custom_keys:
|
||||||
with ui.row().classes('w-full items-center'):
|
with ui.row().classes('w-full items-center'):
|
||||||
|
|||||||
@@ -215,8 +215,8 @@ class TestProjectSource:
|
|||||||
|
|
||||||
def test_outputs_sequence_number(self):
|
def test_outputs_sequence_number(self):
|
||||||
from project_loader import ProjectSource
|
from project_loader import ProjectSource
|
||||||
assert ProjectSource.RETURN_TYPES == ("INT",)
|
assert ProjectSource.RETURN_TYPES == ("INT", "STRING",)
|
||||||
assert ProjectSource.RETURN_NAMES == ("sequence_number",)
|
assert ProjectSource.RETURN_NAMES == ("sequence_number", "file_name",)
|
||||||
|
|
||||||
def test_hold_config_returns_sequence_number(self):
|
def test_hold_config_returns_sequence_number(self):
|
||||||
from project_loader import ProjectSource
|
from project_loader import ProjectSource
|
||||||
@@ -228,7 +228,7 @@ class TestProjectSource:
|
|||||||
sequence_number=42,
|
sequence_number=42,
|
||||||
label="my_source"
|
label="my_source"
|
||||||
)
|
)
|
||||||
assert result == (42,)
|
assert result == (42, "batch_i2v")
|
||||||
|
|
||||||
def test_category(self):
|
def test_category(self):
|
||||||
from project_loader import ProjectSource
|
from project_loader import ProjectSource
|
||||||
@@ -344,11 +344,102 @@ class TestProjectKey:
|
|||||||
assert ProjectKey.CATEGORY == "utils/json/project"
|
assert ProjectKey.CATEGORY == "utils/json/project"
|
||||||
|
|
||||||
|
|
||||||
|
class TestProjectResolution:
|
||||||
|
def test_input_types(self):
|
||||||
|
from project_loader import ProjectResolution
|
||||||
|
inputs = ProjectResolution.INPUT_TYPES()
|
||||||
|
assert "source_label" in inputs["required"]
|
||||||
|
assert "key_name" in inputs["required"]
|
||||||
|
assert "index" in inputs["required"]
|
||||||
|
assert inputs["required"]["index"][0] == "INT"
|
||||||
|
|
||||||
|
def test_two_outputs(self):
|
||||||
|
from project_loader import ProjectResolution
|
||||||
|
assert ProjectResolution.RETURN_TYPES == ("INT", "INT")
|
||||||
|
assert ProjectResolution.RETURN_NAMES == ("width", "height")
|
||||||
|
|
||||||
|
def test_fetch_resolution_basic(self):
|
||||||
|
from project_loader import ProjectResolution
|
||||||
|
node = ProjectResolution()
|
||||||
|
data = {"resolutions": [[512, 512], [768, 1344], [1344, 768]]}
|
||||||
|
with patch("project_loader._fetch_data", return_value=data):
|
||||||
|
result = node.fetch_resolution(
|
||||||
|
source_label="src", key_name="resolutions", index=1,
|
||||||
|
manager_url="http://localhost:8080", project_name="p",
|
||||||
|
file_name="f", sequence_number=1,
|
||||||
|
)
|
||||||
|
assert result == (768, 1344)
|
||||||
|
|
||||||
|
def test_fetch_resolution_index_zero(self):
|
||||||
|
from project_loader import ProjectResolution
|
||||||
|
node = ProjectResolution()
|
||||||
|
data = {"resolutions": [[512, 512], [1024, 1024]]}
|
||||||
|
with patch("project_loader._fetch_data", return_value=data):
|
||||||
|
result = node.fetch_resolution(
|
||||||
|
source_label="src", key_name="resolutions", index=0,
|
||||||
|
manager_url="http://localhost:8080", project_name="p",
|
||||||
|
file_name="f", sequence_number=1,
|
||||||
|
)
|
||||||
|
assert result == (512, 512)
|
||||||
|
|
||||||
|
def test_fetch_resolution_clamps_on_out_of_bounds(self):
|
||||||
|
from project_loader import ProjectResolution
|
||||||
|
node = ProjectResolution()
|
||||||
|
data = {"resolutions": [[512, 512], [1024, 1024]]}
|
||||||
|
with patch("project_loader._fetch_data", return_value=data):
|
||||||
|
result = node.fetch_resolution(
|
||||||
|
source_label="src", key_name="resolutions", index=99,
|
||||||
|
manager_url="http://localhost:8080", project_name="p",
|
||||||
|
file_name="f", sequence_number=1,
|
||||||
|
)
|
||||||
|
assert result == (1024, 1024) # last entry
|
||||||
|
|
||||||
|
def test_fetch_resolution_missing_key_returns_defaults(self):
|
||||||
|
from project_loader import ProjectResolution
|
||||||
|
node = ProjectResolution()
|
||||||
|
with patch("project_loader._fetch_data", return_value={}):
|
||||||
|
result = node.fetch_resolution(
|
||||||
|
source_label="src", key_name="nonexistent", index=0,
|
||||||
|
manager_url="http://localhost:8080", project_name="p",
|
||||||
|
file_name="f", sequence_number=1,
|
||||||
|
)
|
||||||
|
assert result == (512, 512)
|
||||||
|
|
||||||
|
def test_fetch_resolution_network_error_returns_defaults(self):
|
||||||
|
from project_loader import ProjectResolution
|
||||||
|
node = ProjectResolution()
|
||||||
|
error_resp = {"error": "network_error", "message": "Connection refused"}
|
||||||
|
with patch("project_loader._fetch_data", return_value=error_resp):
|
||||||
|
result = node.fetch_resolution(
|
||||||
|
source_label="src", key_name="resolutions", index=0,
|
||||||
|
manager_url="http://localhost:8080", project_name="p",
|
||||||
|
file_name="f", sequence_number=1,
|
||||||
|
)
|
||||||
|
assert result == (512, 512)
|
||||||
|
|
||||||
|
def test_fetch_resolution_malformed_entry_returns_defaults(self):
|
||||||
|
from project_loader import ProjectResolution
|
||||||
|
node = ProjectResolution()
|
||||||
|
data = {"resolutions": [[512]]} # single-element, not a valid pair
|
||||||
|
with patch("project_loader._fetch_data", return_value=data):
|
||||||
|
result = node.fetch_resolution(
|
||||||
|
source_label="src", key_name="resolutions", index=0,
|
||||||
|
manager_url="http://localhost:8080", project_name="p",
|
||||||
|
file_name="f", sequence_number=1,
|
||||||
|
)
|
||||||
|
assert result == (512, 512)
|
||||||
|
|
||||||
|
def test_category(self):
|
||||||
|
from project_loader import ProjectResolution
|
||||||
|
assert ProjectResolution.CATEGORY == "utils/json/project"
|
||||||
|
|
||||||
|
|
||||||
class TestNodeMappings:
|
class TestNodeMappings:
|
||||||
def test_mappings_exist(self):
|
def test_mappings_exist(self):
|
||||||
from project_loader import PROJECT_NODE_CLASS_MAPPINGS, PROJECT_NODE_DISPLAY_NAME_MAPPINGS
|
from project_loader import PROJECT_NODE_CLASS_MAPPINGS, PROJECT_NODE_DISPLAY_NAME_MAPPINGS
|
||||||
assert "ProjectLoaderDynamic" in PROJECT_NODE_CLASS_MAPPINGS
|
assert "ProjectLoaderDynamic" in PROJECT_NODE_CLASS_MAPPINGS
|
||||||
assert "ProjectSource" in PROJECT_NODE_CLASS_MAPPINGS
|
assert "ProjectSource" in PROJECT_NODE_CLASS_MAPPINGS
|
||||||
assert "ProjectKey" in PROJECT_NODE_CLASS_MAPPINGS
|
assert "ProjectKey" in PROJECT_NODE_CLASS_MAPPINGS
|
||||||
assert len(PROJECT_NODE_CLASS_MAPPINGS) == 3
|
assert "ProjectResolution" in PROJECT_NODE_CLASS_MAPPINGS
|
||||||
assert len(PROJECT_NODE_DISPLAY_NAME_MAPPINGS) == 3
|
assert len(PROJECT_NODE_CLASS_MAPPINGS) == 4
|
||||||
|
assert len(PROJECT_NODE_DISPLAY_NAME_MAPPINGS) == 4
|
||||||
|
|||||||
@@ -0,0 +1,191 @@
|
|||||||
|
import { app } from "../../scripts/app.js";
|
||||||
|
import { api } from "../../scripts/api.js";
|
||||||
|
|
||||||
|
app.registerExtension({
|
||||||
|
name: "json.manager.project.resolution",
|
||||||
|
|
||||||
|
async beforeQueuePrompt() {
|
||||||
|
if (!app.graph?._nodes) return;
|
||||||
|
for (const node of app.graph._nodes) {
|
||||||
|
if (node.type === "ProjectResolution" && node._syncFromSource) {
|
||||||
|
node._syncFromSource();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async beforeRegisterNodeDef(nodeType, nodeData, app) {
|
||||||
|
if (nodeData.name !== "ProjectResolution") return;
|
||||||
|
|
||||||
|
function hideWidget(widget) {
|
||||||
|
if (widget.origType === undefined) widget.origType = widget.type;
|
||||||
|
widget.type = "hidden";
|
||||||
|
widget.hidden = true;
|
||||||
|
widget.computeSize = () => [0, -4];
|
||||||
|
}
|
||||||
|
|
||||||
|
function replaceWithCombo(node, name, values, callback) {
|
||||||
|
const idx = node.widgets?.findIndex(w => w.name === name);
|
||||||
|
if (idx === -1 || idx === undefined) return null;
|
||||||
|
const oldWidget = node.widgets[idx];
|
||||||
|
const savedValue = oldWidget.value || "";
|
||||||
|
const comboValues = values.length > 0 ? values : [""];
|
||||||
|
if (savedValue && !comboValues.includes(savedValue)) {
|
||||||
|
comboValues.unshift(savedValue);
|
||||||
|
}
|
||||||
|
const defaultValue = savedValue || comboValues[0];
|
||||||
|
node.widgets.splice(idx, 1);
|
||||||
|
const combo = node.addWidget("combo", name, defaultValue, callback, { values: comboValues });
|
||||||
|
if (node.widgets.length > 1) {
|
||||||
|
node.widgets.splice(node.widgets.length - 1, 1);
|
||||||
|
node.widgets.splice(idx, 0, combo);
|
||||||
|
}
|
||||||
|
return combo;
|
||||||
|
}
|
||||||
|
|
||||||
|
const origOnNodeCreated = nodeType.prototype.onNodeCreated;
|
||||||
|
nodeType.prototype.onNodeCreated = function () {
|
||||||
|
origOnNodeCreated?.apply(this, arguments);
|
||||||
|
this._configured = false;
|
||||||
|
|
||||||
|
// Hide synced config widgets — index stays visible, user wires it from loop node
|
||||||
|
for (const name of ["manager_url", "project_name", "file_name", "sequence_number"]) {
|
||||||
|
const w = this.widgets?.find(w => w.name === name);
|
||||||
|
if (w) hideWidget(w);
|
||||||
|
}
|
||||||
|
|
||||||
|
const node = this;
|
||||||
|
const sourceLabels = this._getSourceLabels?.() || [];
|
||||||
|
const srcCombo = replaceWithCombo(this, "source_label", sourceLabels, function (value) {
|
||||||
|
node._syncFromSource();
|
||||||
|
node._refreshKeys();
|
||||||
|
});
|
||||||
|
if (srcCombo) srcCombo.value = sourceLabels[0] || "";
|
||||||
|
|
||||||
|
const keyCombo = replaceWithCombo(this, "key_name", [], function (value) {
|
||||||
|
node.title = value ? `Resolution: ${value}` : "Project Resolution";
|
||||||
|
app.graph?.setDirtyCanvas(true, true);
|
||||||
|
});
|
||||||
|
if (keyCombo) keyCombo.value = "";
|
||||||
|
|
||||||
|
queueMicrotask(() => {
|
||||||
|
if (!this._configured) {
|
||||||
|
this.setSize(this.computeSize());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
nodeType.prototype._getSourceLabels = function () {
|
||||||
|
const seen = new Set();
|
||||||
|
const labels = [];
|
||||||
|
if (!this.graph) return labels;
|
||||||
|
for (const node of this.graph._nodes) {
|
||||||
|
if (node.type === "ProjectSource") {
|
||||||
|
const lw = node.widgets?.find(w => w.name === "label");
|
||||||
|
if (lw?.value && !seen.has(lw.value)) {
|
||||||
|
seen.add(lw.value);
|
||||||
|
labels.push(lw.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return labels;
|
||||||
|
};
|
||||||
|
|
||||||
|
nodeType.prototype._findSource = function (label) {
|
||||||
|
if (!this.graph || !label) return null;
|
||||||
|
for (const node of this.graph._nodes) {
|
||||||
|
if (node.type === "ProjectSource") {
|
||||||
|
const lw = node.widgets?.find(w => w.name === "label");
|
||||||
|
if (lw?.value === label) return node;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
nodeType.prototype._syncFromSource = function () {
|
||||||
|
const srcWidget = this.widgets?.find(w => w.name === "source_label");
|
||||||
|
const source = this._findSource(srcWidget?.value);
|
||||||
|
if (!source) return;
|
||||||
|
for (const name of ["manager_url", "project_name", "file_name", "sequence_number"]) {
|
||||||
|
const dst = this.widgets?.find(w => w.name === name);
|
||||||
|
const src = source.widgets?.find(w => w.name === name);
|
||||||
|
if (dst && src) dst.value = src.value;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
nodeType.prototype._refreshKeys = async function () {
|
||||||
|
const urlW = this.widgets?.find(w => w.name === "manager_url");
|
||||||
|
const projW = this.widgets?.find(w => w.name === "project_name");
|
||||||
|
const fileW = this.widgets?.find(w => w.name === "file_name");
|
||||||
|
const seqW = this.widgets?.find(w => w.name === "sequence_number");
|
||||||
|
if (!urlW?.value || !projW?.value || !fileW?.value) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const resp = await api.fetchApi(
|
||||||
|
`/json_manager/get_project_keys?url=${encodeURIComponent(urlW.value)}&project=${encodeURIComponent(projW.value)}&file=${encodeURIComponent(fileW.value)}&seq=${seqW?.value || 1}`
|
||||||
|
);
|
||||||
|
if (!resp.ok) return;
|
||||||
|
const data = await resp.json();
|
||||||
|
if (data.error || !Array.isArray(data.keys)) return;
|
||||||
|
|
||||||
|
const keyWidget = this.widgets?.find(w => w.name === "key_name");
|
||||||
|
if (keyWidget) {
|
||||||
|
keyWidget.options.values = data.keys.length > 0 ? data.keys : [""];
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error("[ProjectResolution] Failed to refresh keys:", e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const origOnMouseDown = nodeType.prototype.onMouseDown;
|
||||||
|
nodeType.prototype.onMouseDown = function (e, localPos, graphCanvas) {
|
||||||
|
origOnMouseDown?.apply(this, arguments);
|
||||||
|
const srcWidget = this.widgets?.find(w => w.name === "source_label");
|
||||||
|
if (srcWidget) srcWidget.options.values = this._getSourceLabels();
|
||||||
|
this._syncFromSource();
|
||||||
|
};
|
||||||
|
|
||||||
|
const origOnConfigure = nodeType.prototype.onConfigure;
|
||||||
|
nodeType.prototype.onConfigure = function (info) {
|
||||||
|
origOnConfigure?.apply(this, arguments);
|
||||||
|
this._configured = true;
|
||||||
|
|
||||||
|
for (const name of ["manager_url", "project_name", "file_name", "sequence_number"]) {
|
||||||
|
const w = this.widgets?.find(w => w.name === name);
|
||||||
|
if (w) hideWidget(w);
|
||||||
|
}
|
||||||
|
|
||||||
|
const srcWidget = this.widgets?.find(w => w.name === "source_label");
|
||||||
|
if (srcWidget && srcWidget.type !== "combo") {
|
||||||
|
const node = this;
|
||||||
|
replaceWithCombo(this, "source_label", this._getSourceLabels(), function (value) {
|
||||||
|
node._syncFromSource();
|
||||||
|
node._refreshKeys();
|
||||||
|
});
|
||||||
|
} else if (srcWidget) {
|
||||||
|
srcWidget.options.values = this._getSourceLabels();
|
||||||
|
}
|
||||||
|
|
||||||
|
const keyWidget = this.widgets?.find(w => w.name === "key_name");
|
||||||
|
if (keyWidget && keyWidget.type !== "combo") {
|
||||||
|
const node = this;
|
||||||
|
replaceWithCombo(this, "key_name", [], function (value) {
|
||||||
|
node.title = value ? `Resolution: ${value}` : "Project Resolution";
|
||||||
|
app.graph?.setDirtyCanvas(true, true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const finalKeyWidget = this.widgets?.find(w => w.name === "key_name");
|
||||||
|
if (finalKeyWidget?.value) {
|
||||||
|
this.title = `Resolution: ${finalKeyWidget.value}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setSize(this.computeSize());
|
||||||
|
|
||||||
|
const node = this;
|
||||||
|
queueMicrotask(() => {
|
||||||
|
node._syncFromSource();
|
||||||
|
node._refreshKeys();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user