feat: improve workflow tab click detection and stable graph signing
- Add isLikelyWorkflowTabClickTarget to distinguish workflow tab clicks from generic document clicks, preventing false 'workflow-change' triggers and unnecessary refresh cycles. - Add getWorkflowSignatureData to produce a stable, normalized signature for workflow data (sorted nodes/links, top-level signature included). - Update document-click handler to use a new 'workflow-tab-click' reason via the click-target heuristic. - Simplify refreshForActiveWorkflowChange retry logic by removing the pending-graph-switch special case, since click origin is now explicit.
This commit is contained in:
@@ -246,8 +246,9 @@ export class ModelResolver {
|
||||
|
||||
const target = event.target instanceof Element ? event.target : null;
|
||||
if (target?.closest('#model-resolver-modal, .model-resolver-backdrop')) return;
|
||||
if (!owner.isLikelyWorkflowTabClickTarget(target)) return;
|
||||
|
||||
owner.handleActiveWorkflowRouteChange('document-click');
|
||||
owner.handleActiveWorkflowRouteChange('workflow-tab-click');
|
||||
};
|
||||
const focusHandler = () => {
|
||||
window.__ModelResolverWorkflowChangeOwner?.handleActiveWorkflowRouteChange('window-focus');
|
||||
@@ -277,6 +278,28 @@ export class ModelResolver {
|
||||
];
|
||||
}
|
||||
|
||||
isLikelyWorkflowTabClickTarget(target) {
|
||||
if (!(target instanceof Element)) return false;
|
||||
|
||||
const tab = target.closest([
|
||||
'[data-workflow-id]',
|
||||
'[data-workflow-name]',
|
||||
'[data-tab-id*="workflow" i]',
|
||||
'[aria-controls*="workflow" i]',
|
||||
'[class*="workflow"][class*="tab" i]',
|
||||
'[class*="tab"][class*="workflow" i]',
|
||||
'[class*="workflow"][class*="item" i]'
|
||||
].join(','));
|
||||
if (tab) return true;
|
||||
|
||||
const roleTab = target.closest('[role="tab"]');
|
||||
if (!roleTab) return false;
|
||||
|
||||
const text = roleTab.textContent?.trim() || '';
|
||||
const aria = roleTab.getAttribute('aria-label') || roleTab.getAttribute('title') || '';
|
||||
return /workflow|unsaved|untitled/i.test(`${text} ${aria}`);
|
||||
}
|
||||
|
||||
handleActiveWorkflowRouteChange(reason = 'workflow-change') {
|
||||
if (!this.dialog?.isVisible()) return;
|
||||
this.dialog.scheduleActiveWorkflowRefresh(reason);
|
||||
|
||||
@@ -6,13 +6,61 @@ export const workflowStateMethods = {
|
||||
getWorkflowSignature(workflow) {
|
||||
if (!workflow) return null;
|
||||
try {
|
||||
return JSON.stringify(workflow);
|
||||
return JSON.stringify(this.getWorkflowSignatureData(workflow));
|
||||
} catch (error) {
|
||||
console.warn('Model Resolver: workflow signature generation failed', error);
|
||||
return null;
|
||||
}
|
||||
},
|
||||
|
||||
getWorkflowSignatureData(workflow) {
|
||||
const normalizeNode = (node = {}) => ({
|
||||
id: node.id,
|
||||
type: node.type,
|
||||
mode: node.mode,
|
||||
flags: node.flags,
|
||||
inputs: Array.isArray(node.inputs)
|
||||
? node.inputs.map(input => ({
|
||||
name: input?.name,
|
||||
type: input?.type,
|
||||
link: input?.link
|
||||
}))
|
||||
: [],
|
||||
outputs: Array.isArray(node.outputs)
|
||||
? node.outputs.map(output => ({
|
||||
name: output?.name,
|
||||
type: output?.type,
|
||||
links: output?.links || []
|
||||
}))
|
||||
: [],
|
||||
widgets_values: node.widgets_values || [],
|
||||
properties: node.properties || {}
|
||||
});
|
||||
|
||||
const normalizeDefinition = (definition = {}) => ({
|
||||
nodes: Array.isArray(definition.nodes)
|
||||
? definition.nodes.map(normalizeNode).sort((a, b) => String(a.id).localeCompare(String(b.id)))
|
||||
: [],
|
||||
links: definition.links || []
|
||||
});
|
||||
|
||||
const definitions = workflow.definitions && typeof workflow.definitions === 'object'
|
||||
? Object.fromEntries(
|
||||
Object.entries(workflow.definitions)
|
||||
.sort(([a], [b]) => String(a).localeCompare(String(b)))
|
||||
.map(([key, definition]) => [key, normalizeDefinition(definition)])
|
||||
)
|
||||
: {};
|
||||
|
||||
return {
|
||||
nodes: Array.isArray(workflow.nodes)
|
||||
? workflow.nodes.map(normalizeNode).sort((a, b) => String(a.id).localeCompare(String(b.id)))
|
||||
: [],
|
||||
links: workflow.links || [],
|
||||
definitions
|
||||
};
|
||||
},
|
||||
|
||||
getActiveWorkflowRouteKey() {
|
||||
try {
|
||||
return window.location?.hash || '';
|
||||
@@ -217,14 +265,8 @@ export const workflowStateMethods = {
|
||||
const routeChanged = currentRoute !== this.activeWorkflowRouteKey;
|
||||
const signatureChanged = signature && signature !== this.activeWorkflowSignature;
|
||||
const graphStillLooksOld = routeChanged && previousSignature && signature === previousSignature;
|
||||
const possiblePendingGraphSwitch = (
|
||||
reason === 'document-click' &&
|
||||
previousSignature &&
|
||||
signature === previousSignature
|
||||
);
|
||||
const maxAttempts = possiblePendingGraphSwitch ? 3 : 8;
|
||||
|
||||
if ((!signature || graphStillLooksOld || possiblePendingGraphSwitch) && attempt < maxAttempts) {
|
||||
if ((!signature || graphStillLooksOld) && attempt < 8) {
|
||||
setTimeout(() => {
|
||||
this.refreshForActiveWorkflowChange({
|
||||
reason,
|
||||
|
||||
Reference in New Issue
Block a user