haybale-haystack 0.2.4__tar.gz → 0.3.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {haybale_haystack-0.2.4 → haybale_haystack-0.3.0}/PKG-INFO +5 -5
- {haybale_haystack-0.2.4 → haybale_haystack-0.3.0}/README.md +6 -5
- {haybale_haystack-0.2.4 → haybale_haystack-0.3.0}/haybale_haystack/OVERVIEW.md +6 -5
- {haybale_haystack-0.2.4 → haybale_haystack-0.3.0}/haybale_haystack/QUICKREF.md +7 -6
- haybale_haystack-0.3.0/haybale_haystack/docs/haybale-haystack.panel.EditMacroMenuPanel.md +18 -0
- {haybale_haystack-0.2.4 → haybale_haystack-0.3.0}/haybale_haystack/docs/haybale-haystack.panel.GraphRunSettingsPanel.md +2 -0
- {haybale_haystack-0.2.4 → haybale_haystack-0.3.0}/haybale_haystack/docs/haybale-haystack.panel.OpenInHaystackMenuPanel.md +2 -0
- haybale_haystack-0.3.0/haybale_haystack/docs/haybale-haystack.panel.UpdateMacroMenuPanel.md +21 -0
- {haybale_haystack-0.2.4 → haybale_haystack-0.3.0}/haybale_haystack/docs/haybale-haystack.setting.HaystackSettings.md +2 -0
- {haybale_haystack-0.2.4 → haybale_haystack-0.3.0}/haybale_haystack/docs/haybale-haystack.state.HaystackState.md +2 -0
- {haybale_haystack-0.2.4 → haybale_haystack-0.3.0}/haybale_haystack/editors/haystack_editor.py +87 -25
- {haybale_haystack-0.2.4 → haybale_haystack-0.3.0}/haybale_haystack/farmhands/graph_tools.py +4 -9
- {haybale_haystack-0.2.4 → haybale_haystack-0.3.0}/haybale_haystack/graph_entry.py +26 -1
- {haybale_haystack-0.2.4 → haybale_haystack-0.3.0}/haybale_haystack/haybale.toml +1 -1
- haybale_haystack-0.3.0/haybale_haystack/panels/__init__.py +11 -0
- {haybale_haystack-0.2.4 → haybale_haystack-0.3.0}/haybale_haystack/panels/file_browser/menu/file.py +1 -0
- haybale_haystack-0.3.0/haybale_haystack/panels/graph/menu/macro.py +269 -0
- {haybale_haystack-0.2.4 → haybale_haystack-0.3.0}/haybale_haystack/panels/properties/introspect/graph_run_settings_panel.py +2 -1
- {haybale_haystack-0.2.4 → haybale_haystack-0.3.0}/haybale_haystack/settings/haystack_settings.py +1 -1
- {haybale_haystack-0.2.4 → haybale_haystack-0.3.0}/haybale_haystack/state/haystack_state.py +137 -21
- {haybale_haystack-0.2.4 → haybale_haystack-0.3.0}/pyproject.toml +5 -5
- haybale_haystack-0.2.4/haybale_haystack/docs/haybale-haystack.panel.EditMacroMenuPanel.md +0 -16
- haybale_haystack-0.2.4/haybale_haystack/panels/__init__.py +0 -11
- haybale_haystack-0.2.4/haybale_haystack/panels/graph/menu/macro.py +0 -105
- {haybale_haystack-0.2.4 → haybale_haystack-0.3.0}/.gitignore +0 -0
- {haybale_haystack-0.2.4 → haybale_haystack-0.3.0}/haybale_haystack/__init__.py +0 -0
- {haybale_haystack-0.2.4 → haybale_haystack-0.3.0}/haybale_haystack/docs/haybale-haystack.editor.HaystackEditor.md +0 -0
- {haybale_haystack-0.2.4 → haybale_haystack-0.3.0}/haybale_haystack/docs/haybale-haystack.farmhand.close_graph.md +0 -0
- {haybale_haystack-0.2.4 → haybale_haystack-0.3.0}/haybale_haystack/docs/haybale-haystack.farmhand.compile_graph.md +0 -0
- {haybale_haystack-0.2.4 → haybale_haystack-0.3.0}/haybale_haystack/docs/haybale-haystack.farmhand.create_graph.md +0 -0
- {haybale_haystack-0.2.4 → haybale_haystack-0.3.0}/haybale_haystack/docs/haybale-haystack.farmhand.list_graphs.md +0 -0
- {haybale_haystack-0.2.4 → haybale_haystack-0.3.0}/haybale_haystack/docs/haybale-haystack.farmhand.open_graph.md +0 -0
- {haybale_haystack-0.2.4 → haybale_haystack-0.3.0}/haybale_haystack/docs/haybale-haystack.farmhand.rename_graph.md +0 -0
- {haybale_haystack-0.2.4 → haybale_haystack-0.3.0}/haybale_haystack/docs/haybale-haystack.farmhand.save_graph.md +0 -0
- {haybale_haystack-0.2.4 → haybale_haystack-0.3.0}/haybale_haystack/docs/haybale-haystack.farmhand.start_graph.md +0 -0
- {haybale_haystack-0.2.4 → haybale_haystack-0.3.0}/haybale_haystack/docs/haybale-haystack.farmhand.stop_graph.md +0 -0
- {haybale_haystack-0.2.4 → haybale_haystack-0.3.0}/haybale_haystack/editors/__init__.py +0 -0
- {haybale_haystack-0.2.4 → haybale_haystack-0.3.0}/haybale_haystack/farmhands/__init__.py +0 -0
- {haybale_haystack-0.2.4 → haybale_haystack-0.3.0}/haybale_haystack/persistence.py +0 -0
- {haybale_haystack-0.2.4 → haybale_haystack-0.3.0}/haybale_haystack/settings/__init__.py +0 -0
- {haybale_haystack-0.2.4 → haybale_haystack-0.3.0}/haybale_haystack/settings/graph_run_settings.py +0 -0
- {haybale_haystack-0.2.4 → haybale_haystack-0.3.0}/haybale_haystack/signals.py +0 -0
- {haybale_haystack-0.2.4 → haybale_haystack-0.3.0}/haybale_haystack/state/__init__.py +0 -0
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: haybale-haystack
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: File-centric multi-graph manager
|
|
5
5
|
License: MIT
|
|
6
6
|
Keywords: graph-management
|
|
7
7
|
Requires-Python: >=3.12
|
|
8
|
-
Requires-Dist: haybale-graph-editor>=0.
|
|
9
|
-
Requires-Dist: haybale-studio>=0.
|
|
10
|
-
Requires-Dist: haywire-core>=0.
|
|
11
|
-
Requires-Dist: nicegui>=3.
|
|
8
|
+
Requires-Dist: haybale-graph-editor>=0.3.0
|
|
9
|
+
Requires-Dist: haybale-studio>=0.3.0
|
|
10
|
+
Requires-Dist: haywire-core>=0.3.0
|
|
11
|
+
Requires-Dist: nicegui>=3.18.0
|
|
12
12
|
Requires-Dist: toml>=0.10.2
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
File-centric multi-graph manager
|
|
8
8
|
|
|
9
9
|
## Settings
|
|
10
|
-
- **Haystack** —
|
|
10
|
+
- **Haystack** — Per-workspace settings for haystack scalars.
|
|
11
11
|
|
|
12
12
|
## Farmhands
|
|
13
13
|
- **Close graph** — Close an open graph entry. NEVER deletes the file on disk.
|
|
@@ -21,12 +21,13 @@ File-centric multi-graph manager
|
|
|
21
21
|
- **Stop graph** — Stop a running graph (bounded grace, then teardown).
|
|
22
22
|
|
|
23
23
|
## States
|
|
24
|
-
- **Haystack State** —
|
|
24
|
+
- **Haystack State** — In-memory registry of open graphs, one entry per file path.
|
|
25
25
|
|
|
26
26
|
## Panels
|
|
27
|
-
- **Edit Macro** —
|
|
28
|
-
- **Graph Settings** —
|
|
29
|
-
- **Load into Haystack** —
|
|
27
|
+
- **Edit Macro** — Open the document behind a macro placement, in its own editor tab.
|
|
28
|
+
- **Graph Settings** — Renders the GraphRunSettings for the currently active graph.
|
|
29
|
+
- **Load into Haystack** — Load a .haywire graph file into the Haystack (does not open an editor tab).
|
|
30
|
+
- **Update Macro** — Send a modified instance's changes back to the macro it came from.
|
|
30
31
|
|
|
31
32
|
## Editors
|
|
32
33
|
- **Haystack** — All open graphs. Click to switch; "+" to create a new graph.
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
File-centric multi-graph manager
|
|
4
4
|
|
|
5
5
|
## Settings
|
|
6
|
-
- **Haystack** —
|
|
6
|
+
- **Haystack** — Per-workspace settings for haystack scalars.
|
|
7
7
|
|
|
8
8
|
## Farmhands
|
|
9
9
|
- **Close graph** — Close an open graph entry. NEVER deletes the file on disk.
|
|
@@ -17,12 +17,13 @@ File-centric multi-graph manager
|
|
|
17
17
|
- **Stop graph** — Stop a running graph (bounded grace, then teardown).
|
|
18
18
|
|
|
19
19
|
## States
|
|
20
|
-
- **Haystack State** —
|
|
20
|
+
- **Haystack State** — In-memory registry of open graphs, one entry per file path.
|
|
21
21
|
|
|
22
22
|
## Panels
|
|
23
|
-
- **Edit Macro** —
|
|
24
|
-
- **Graph Settings** —
|
|
25
|
-
- **Load into Haystack** —
|
|
23
|
+
- **Edit Macro** — Open the document behind a macro placement, in its own editor tab.
|
|
24
|
+
- **Graph Settings** — Renders the GraphRunSettings for the currently active graph.
|
|
25
|
+
- **Load into Haystack** — Load a .haywire graph file into the Haystack (does not open an editor tab).
|
|
26
|
+
- **Update Macro** — Send a modified instance's changes back to the macro it came from.
|
|
26
27
|
|
|
27
28
|
## Editors
|
|
28
29
|
- **Haystack** — All open graphs. Click to switch; "+" to create a new graph.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
# haybale-haystack — component index (v0.
|
|
1
|
+
# haybale-haystack — component index (v0.3.0)
|
|
2
2
|
|
|
3
3
|
## setting
|
|
4
|
-
- `haybale-haystack:setting:HaystackSettings` — Haystack —
|
|
4
|
+
- `haybale-haystack:setting:HaystackSettings` — Haystack — Per-workspace settings for haystack scalars.
|
|
5
5
|
|
|
6
6
|
## farmhand
|
|
7
7
|
- `haybale-haystack:farmhand:close_graph` — Close graph — Close an open graph entry. NEVER deletes the file on disk.
|
|
@@ -15,12 +15,13 @@
|
|
|
15
15
|
- `haybale-haystack:farmhand:stop_graph` — Stop graph — Stop a running graph (bounded grace, then teardown).
|
|
16
16
|
|
|
17
17
|
## state
|
|
18
|
-
- `haybale-haystack:state:HaystackState` — Haystack State —
|
|
18
|
+
- `haybale-haystack:state:HaystackState` — Haystack State — In-memory registry of open graphs, one entry per file path.
|
|
19
19
|
|
|
20
20
|
## panel
|
|
21
|
-
- `haybale-haystack:panel:EditMacroMenuPanel` — Edit Macro —
|
|
22
|
-
- `haybale-haystack:panel:GraphRunSettingsPanel` — Graph Settings —
|
|
23
|
-
- `haybale-haystack:panel:OpenInHaystackMenuPanel` — Load into Haystack —
|
|
21
|
+
- `haybale-haystack:panel:EditMacroMenuPanel` — Edit Macro — Open the document behind a macro placement, in its own editor tab.
|
|
22
|
+
- `haybale-haystack:panel:GraphRunSettingsPanel` — Graph Settings — Renders the GraphRunSettings for the currently active graph.
|
|
23
|
+
- `haybale-haystack:panel:OpenInHaystackMenuPanel` — Load into Haystack — Load a .haywire graph file into the Haystack (does not open an editor tab).
|
|
24
|
+
- `haybale-haystack:panel:UpdateMacroMenuPanel` — Update Macro — Send a modified instance's changes back to the macro it came from.
|
|
24
25
|
|
|
25
26
|
## editor
|
|
26
27
|
- `haybale-haystack:editor:HaystackEditor` — Haystack — All open graphs. Click to switch; "+" to create a new graph.
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# Edit Macro
|
|
2
|
+
|
|
3
|
+
`haybale-haystack:panel:EditMacroMenuPanel` · kind: panel
|
|
4
|
+
|
|
5
|
+
Open the document behind a macro placement, in its own editor tab.
|
|
6
|
+
|
|
7
|
+
## Details
|
|
8
|
+
|
|
9
|
+
- **surface**: `selection-macro`
|
|
10
|
+
- **order**: `10`
|
|
11
|
+
|
|
12
|
+
## Notes
|
|
13
|
+
|
|
14
|
+
Open the document behind a macro placement, in its own editor tab.
|
|
15
|
+
|
|
16
|
+
Only visible on a placement. Its interior is runtime state rebuilt from the
|
|
17
|
+
template, so it cannot be edited in place; this row opens the document that
|
|
18
|
+
can.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# Update Macro
|
|
2
|
+
|
|
3
|
+
`haybale-haystack:panel:UpdateMacroMenuPanel` · kind: panel
|
|
4
|
+
|
|
5
|
+
Send a modified instance's changes back to the macro it came from.
|
|
6
|
+
|
|
7
|
+
## Details
|
|
8
|
+
|
|
9
|
+
- **surface**: `selection-macro`
|
|
10
|
+
- **order**: `30`
|
|
11
|
+
|
|
12
|
+
## Notes
|
|
13
|
+
|
|
14
|
+
Send a modified instance's changes back to the macro it came from.
|
|
15
|
+
|
|
16
|
+
Only visible on a Group that was detached from a macro. The card becomes a
|
|
17
|
+
placement of that macro again, and the macro opens in its own tab with the
|
|
18
|
+
instance's contents, unsaved. Saving it updates every placement.
|
|
19
|
+
|
|
20
|
+
Greyed out when the macro is not installed, cannot be edited in place, or
|
|
21
|
+
has unsaved edits in its tab.
|
{haybale_haystack-0.2.4 → haybale_haystack-0.3.0}/haybale_haystack/editors/haystack_editor.py
RENAMED
|
@@ -6,6 +6,7 @@ from typing import Optional
|
|
|
6
6
|
|
|
7
7
|
from nicegui import ui
|
|
8
8
|
|
|
9
|
+
from haywire.core.access import AccessTier, LevelMode
|
|
9
10
|
from haywire.ui import elements as hui
|
|
10
11
|
from haywire.ui.editor import BaseEditor, SlotName, editor
|
|
11
12
|
from haywire.core.session import (
|
|
@@ -19,29 +20,40 @@ from haywire.core.signals import (
|
|
|
19
20
|
BroadcastClose,
|
|
20
21
|
Close,
|
|
21
22
|
GraphDataMutated,
|
|
23
|
+
GraphUnsavedChanged,
|
|
22
24
|
Reveal,
|
|
23
25
|
RevealGraphInstance,
|
|
26
|
+
Save,
|
|
24
27
|
)
|
|
25
28
|
from haywire.ui.modals import confirm_modal, pick_modal, rename_modal
|
|
26
29
|
|
|
27
30
|
from haybale_graph_editor.state.edit_state import EditState
|
|
28
31
|
from haybale_graph_editor.state.graph_app_state import GraphAppState
|
|
29
|
-
from haybale_graph_editor.editors.graph_editor import GraphEditor
|
|
32
|
+
from haybale_graph_editor.editors.graph_editor import GraphEditor, definition_path
|
|
30
33
|
from haybale_graph_editor.editors.graph_save_as import open_graph_save_as_dialog
|
|
31
34
|
|
|
32
|
-
from
|
|
33
|
-
from
|
|
34
|
-
from
|
|
35
|
+
from haybale_haystack.signals import HaystackReloaded, HaystackTeardown
|
|
36
|
+
from haybale_haystack.state.haystack_state import HaystackState
|
|
37
|
+
from haybale_haystack.graph_entry import EntryKind, GraphEntry
|
|
35
38
|
|
|
36
39
|
logger = logging.getLogger(__name__)
|
|
37
40
|
|
|
38
41
|
|
|
42
|
+
def _refused(context: "SessionContext", required: AccessTier, action: str) -> bool:
|
|
43
|
+
"""Return whether the principal lacks ``required``, telling them ``"<action> needs <tier> access"``."""
|
|
44
|
+
if context.can_access(required):
|
|
45
|
+
return False
|
|
46
|
+
ui.notify(f"{action} needs {required.value} access", type="warning", position="top-right")
|
|
47
|
+
return True
|
|
48
|
+
|
|
49
|
+
|
|
39
50
|
@editor(
|
|
40
51
|
label="Haystack",
|
|
41
52
|
icon=hui.icon.haystack,
|
|
42
53
|
default_slot=SlotName.ACTION,
|
|
43
54
|
description='All open graphs. Click to switch; "+" to create a new graph.',
|
|
44
55
|
order=20,
|
|
56
|
+
requires=AccessTier.VIEW,
|
|
45
57
|
)
|
|
46
58
|
class HaystackEditor(BaseEditor):
|
|
47
59
|
"""
|
|
@@ -68,7 +80,7 @@ class HaystackEditor(BaseEditor):
|
|
|
68
80
|
# Signal-bus subscriptions / draw
|
|
69
81
|
# ------------------------------------------------------------------
|
|
70
82
|
|
|
71
|
-
@redraw_on(ActiveGraphMoved, GraphDataMutated, HaystackReloaded)
|
|
83
|
+
@redraw_on(ActiveGraphMoved, GraphDataMutated, GraphUnsavedChanged, HaystackReloaded)
|
|
72
84
|
def _refresh_on_state_move(self, context: "SessionContext", event) -> None:
|
|
73
85
|
"""Empty body — the decorator triggers ``wrapper.redraw()`` after
|
|
74
86
|
this returns, which re-runs ``draw`` and ``_render_list``."""
|
|
@@ -85,25 +97,36 @@ class HaystackEditor(BaseEditor):
|
|
|
85
97
|
"""
|
|
86
98
|
self._on_haystack_teardown(context, event)
|
|
87
99
|
|
|
100
|
+
@react_on(Save)
|
|
101
|
+
def _on_save(self, context: "SessionContext", event: Save) -> None:
|
|
102
|
+
"""Save the loaded haystack in place, or ask for a name when none is loaded."""
|
|
103
|
+
self._on_save_haystack_in_place(context)
|
|
104
|
+
|
|
88
105
|
@react_on(RevealGraphInstance)
|
|
89
106
|
def _on_reveal_graph_instance_reopen(
|
|
90
107
|
self, context: "SessionContext", event: "RevealGraphInstance"
|
|
91
108
|
) -> None:
|
|
92
|
-
"""
|
|
109
|
+
"""Bring forward the tab of the document holding ``event.graph_id``, opening it if needed.
|
|
93
110
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
node/edge in the freshly-constructed GraphEditor, which was not
|
|
98
|
-
alive to receive the original signal.
|
|
111
|
+
The graph may be the document's own or a Group or macro placement interior
|
|
112
|
+
in it, open or not. A tab this opens carries out the reveal when it draws
|
|
113
|
+
(``EditState.pending_reveal``); a tab already open carries it out itself.
|
|
99
114
|
"""
|
|
100
115
|
graph_app_state = context.app_data[GraphAppState]
|
|
101
116
|
for container in graph_app_state.all_containers():
|
|
102
|
-
|
|
117
|
+
graph = container.editor.graph
|
|
118
|
+
if graph.graph_id != event.graph_id and definition_path(graph, event.graph_id) is None:
|
|
119
|
+
continue
|
|
120
|
+
edit_state = context.data[EditState]
|
|
121
|
+
edit_state.pending_reveal = event
|
|
122
|
+
try:
|
|
103
123
|
context.session.publish(
|
|
104
124
|
Reveal(editor=GraphEditor, binding_id=container.binding_id, label=container.display_name)
|
|
105
125
|
)
|
|
106
|
-
|
|
126
|
+
finally:
|
|
127
|
+
# A tab already open draws nothing, so the reveal must not wait for a later draw.
|
|
128
|
+
edit_state.pending_reveal = None
|
|
129
|
+
return
|
|
107
130
|
|
|
108
131
|
def _on_haystack_teardown(self, context: "SessionContext", signal: "HaystackTeardown") -> None:
|
|
109
132
|
"""Translate the teardown fact into local tab-close lifecycle commands.
|
|
@@ -129,7 +152,7 @@ class HaystackEditor(BaseEditor):
|
|
|
129
152
|
|
|
130
153
|
# No on_focus override needed: @redraw_on fires regardless of active-tab
|
|
131
154
|
# state, so backgrounded HaystackEditor instances stay current with
|
|
132
|
-
# ActiveGraphMoved / GraphDataMutated / HaystackReloaded.
|
|
155
|
+
# ActiveGraphMoved / GraphDataMutated / GraphUnsavedChanged / HaystackReloaded.
|
|
133
156
|
|
|
134
157
|
def draw(self, context: "SessionContext", container: ui.element) -> None:
|
|
135
158
|
with container:
|
|
@@ -144,13 +167,16 @@ class HaystackEditor(BaseEditor):
|
|
|
144
167
|
# ------------------------------------------------------------------
|
|
145
168
|
|
|
146
169
|
def _render_header(self, context: "SessionContext") -> None:
|
|
170
|
+
"""Draw the title, unsaved dot and haystack actions; the actions are drawn only for edit."""
|
|
147
171
|
title = self._get_active_haystack_name(context) or "Haystacks"
|
|
148
172
|
hs = context.app_data[HaystackState]
|
|
149
173
|
is_dirty = hs.is_haystack_dirty
|
|
150
|
-
# The dirty dot (when is_dirty=True) is rendered by
|
|
174
|
+
# The dirty dot (when is_dirty=True) is rendered by slot_header
|
|
151
175
|
# itself between the icon and the title — we only add the save
|
|
152
176
|
# icon and the overflow menu on the action side here.
|
|
153
|
-
with hui.
|
|
177
|
+
with hui.slot_header(title, icon=hui.icon.haystack, is_dirty=is_dirty):
|
|
178
|
+
if not context.can_edit():
|
|
179
|
+
return
|
|
154
180
|
# Save icon — paired with the dirty dot; only when dirty.
|
|
155
181
|
if is_dirty:
|
|
156
182
|
hui.icon_action(
|
|
@@ -203,7 +229,7 @@ class HaystackEditor(BaseEditor):
|
|
|
203
229
|
app = context.app
|
|
204
230
|
if app is None:
|
|
205
231
|
with self._list_container:
|
|
206
|
-
|
|
232
|
+
hui.hint("Graph manager not available").classes("p-2 italic")
|
|
207
233
|
return
|
|
208
234
|
|
|
209
235
|
hs = context.app_data[HaystackState]
|
|
@@ -212,7 +238,7 @@ class HaystackEditor(BaseEditor):
|
|
|
212
238
|
|
|
213
239
|
with self._list_container:
|
|
214
240
|
if not graphs:
|
|
215
|
-
|
|
241
|
+
hui.hint("No graphs open").classes("p-2 italic")
|
|
216
242
|
else:
|
|
217
243
|
for entry in graphs:
|
|
218
244
|
self._render_entry(entry, context)
|
|
@@ -221,14 +247,17 @@ class HaystackEditor(BaseEditor):
|
|
|
221
247
|
# haystack and cannot be run, so it does not belong among the
|
|
222
248
|
# entries the play controls and the TOML apply to.
|
|
223
249
|
if macros:
|
|
224
|
-
|
|
250
|
+
hui.section_label("Macros")
|
|
225
251
|
for entry in macros:
|
|
226
252
|
self._render_entry(entry, context)
|
|
227
253
|
|
|
228
|
-
|
|
254
|
+
if context.can_edit():
|
|
255
|
+
self._render_add_bar(context)
|
|
229
256
|
|
|
230
257
|
def _render_entry(self, entry: "GraphEntry", context: "SessionContext") -> None:
|
|
231
258
|
is_active = entry.graph is context.data[EditState].active_graph
|
|
259
|
+
can_edit = context.can_edit()
|
|
260
|
+
can_operate = context.can_access(AccessTier.OPERATE)
|
|
232
261
|
is_unsaved = entry.unsaved or entry.path is None
|
|
233
262
|
is_executing = entry.is_executing
|
|
234
263
|
|
|
@@ -261,9 +290,12 @@ class HaystackEditor(BaseEditor):
|
|
|
261
290
|
# to run, so it carries its kind's icon instead of a control whose
|
|
262
291
|
# only answer would be a refusal.
|
|
263
292
|
if not entry.kind.is_executable():
|
|
264
|
-
ui.icon(hui.icon.
|
|
293
|
+
ui.icon(hui.icon.macro, size="16px").classes("hw-text-dim").tooltip(
|
|
265
294
|
"A macro is run by placing it in a graph"
|
|
266
295
|
)
|
|
296
|
+
elif not can_operate:
|
|
297
|
+
# The row's execution border still shows a running graph.
|
|
298
|
+
pass
|
|
267
299
|
elif is_executing:
|
|
268
300
|
hui.icon_action(
|
|
269
301
|
hui.icon.execution_stop,
|
|
@@ -293,7 +325,7 @@ class HaystackEditor(BaseEditor):
|
|
|
293
325
|
ui.label("not saved").classes("text-xs hw-text-warning-dim")
|
|
294
326
|
|
|
295
327
|
# Save icon — paired with the dirty dot; only when unsaved.
|
|
296
|
-
if is_unsaved:
|
|
328
|
+
if is_unsaved and can_edit:
|
|
297
329
|
hui.icon_action(
|
|
298
330
|
hui.icon.save,
|
|
299
331
|
tooltip="Save",
|
|
@@ -301,7 +333,7 @@ class HaystackEditor(BaseEditor):
|
|
|
301
333
|
)
|
|
302
334
|
|
|
303
335
|
# Overflow menu — hidden during execution
|
|
304
|
-
if not is_executing:
|
|
336
|
+
if can_edit and not is_executing:
|
|
305
337
|
with (
|
|
306
338
|
ui.button(icon="more_vert")
|
|
307
339
|
.props("flat round dense size=xs")
|
|
@@ -352,6 +384,9 @@ class HaystackEditor(BaseEditor):
|
|
|
352
384
|
|
|
353
385
|
def _on_entry_save(self, binding_id: str, context: "SessionContext") -> None:
|
|
354
386
|
"""Save a graph; opens save-as dialog if untitled."""
|
|
387
|
+
if not context.can_edit():
|
|
388
|
+
ui.notify("Saving needs edit access", type="warning", position="top-right")
|
|
389
|
+
return
|
|
355
390
|
app = context.app
|
|
356
391
|
if app is None:
|
|
357
392
|
ui.notify("Graph manager not available", type="warning")
|
|
@@ -393,6 +428,9 @@ class HaystackEditor(BaseEditor):
|
|
|
393
428
|
|
|
394
429
|
def _on_entry_save_as(self, binding_id: str, context: "SessionContext") -> None:
|
|
395
430
|
"""Always open the save-as dialog."""
|
|
431
|
+
if not context.can_edit():
|
|
432
|
+
ui.notify("Saving needs edit access", type="warning", position="top-right")
|
|
433
|
+
return
|
|
396
434
|
app = context.app
|
|
397
435
|
if app is None:
|
|
398
436
|
ui.notify("Graph manager not available", type="warning")
|
|
@@ -416,6 +454,8 @@ class HaystackEditor(BaseEditor):
|
|
|
416
454
|
|
|
417
455
|
def _on_entry_rename(self, binding_id: str, context: "SessionContext") -> None:
|
|
418
456
|
"""Rename: inline edit for file-backed graphs, save-as for untitled."""
|
|
457
|
+
if _refused(context, AccessTier.EDIT, "Renaming"):
|
|
458
|
+
return
|
|
419
459
|
entry = self._resolve_entry(binding_id, context)
|
|
420
460
|
if entry is None:
|
|
421
461
|
return
|
|
@@ -435,6 +475,8 @@ class HaystackEditor(BaseEditor):
|
|
|
435
475
|
confirmation that warns about losing unsaved work. Clean entries
|
|
436
476
|
get a milder confirm that calls out that the file stays on disk.
|
|
437
477
|
"""
|
|
478
|
+
if _refused(context, AccessTier.EDIT, "Removing"):
|
|
479
|
+
return
|
|
438
480
|
entry = self._resolve_entry(binding_id, context)
|
|
439
481
|
if entry is None:
|
|
440
482
|
return
|
|
@@ -498,6 +540,8 @@ class HaystackEditor(BaseEditor):
|
|
|
498
540
|
edit_state = context.data[EditState]
|
|
499
541
|
edit_state.active_graph = None
|
|
500
542
|
edit_state.active_graph_path = None
|
|
543
|
+
edit_state.active_route = None
|
|
544
|
+
context.level_mode = LevelMode.VIEW
|
|
501
545
|
if session:
|
|
502
546
|
session.publish(ActiveGraphMoved())
|
|
503
547
|
|
|
@@ -552,6 +596,8 @@ class HaystackEditor(BaseEditor):
|
|
|
552
596
|
|
|
553
597
|
def _on_new(self, context: "SessionContext") -> None:
|
|
554
598
|
"""Create a new unnamed graph and activate it."""
|
|
599
|
+
if _refused(context, AccessTier.EDIT, "A new graph"):
|
|
600
|
+
return
|
|
555
601
|
app: IAppState = context.app
|
|
556
602
|
session = context.session
|
|
557
603
|
if app is None or session is None:
|
|
@@ -589,6 +635,8 @@ class HaystackEditor(BaseEditor):
|
|
|
589
635
|
|
|
590
636
|
def _on_start_execution(self, binding_id: str, context: "SessionContext") -> None:
|
|
591
637
|
"""Start execution on a graph entry."""
|
|
638
|
+
if _refused(context, AccessTier.OPERATE, "Starting and stopping"):
|
|
639
|
+
return
|
|
592
640
|
entry = self._resolve_entry(binding_id, context)
|
|
593
641
|
if entry is None:
|
|
594
642
|
return
|
|
@@ -603,6 +651,8 @@ class HaystackEditor(BaseEditor):
|
|
|
603
651
|
|
|
604
652
|
def _on_stop_execution(self, binding_id: str, context: "SessionContext") -> None:
|
|
605
653
|
"""Stop execution on a graph entry."""
|
|
654
|
+
if _refused(context, AccessTier.OPERATE, "Starting and stopping"):
|
|
655
|
+
return
|
|
606
656
|
entry = self._resolve_entry(binding_id, context)
|
|
607
657
|
if entry is None:
|
|
608
658
|
return
|
|
@@ -615,7 +665,10 @@ class HaystackEditor(BaseEditor):
|
|
|
615
665
|
# ------------------------------------------------------------------
|
|
616
666
|
|
|
617
667
|
def _on_save_haystack(self, context: "SessionContext") -> None:
|
|
618
|
-
"""Save the current set of open graphs as a haystack ("Save As…")."""
|
|
668
|
+
"""Save the current set of open graphs as a haystack ("Save As…"); refused below edit."""
|
|
669
|
+
if not context.can_edit():
|
|
670
|
+
ui.notify("Saving needs edit access", type="warning", position="top-right")
|
|
671
|
+
return
|
|
619
672
|
app: IAppState = context.app
|
|
620
673
|
if app is None:
|
|
621
674
|
ui.notify("Graph manager not available", type="warning")
|
|
@@ -642,8 +695,11 @@ class HaystackEditor(BaseEditor):
|
|
|
642
695
|
|
|
643
696
|
Falls through to :meth:`_on_save_haystack` (the Save-As modal) when
|
|
644
697
|
no haystack is loaded — there's nothing to overwrite, so the user
|
|
645
|
-
has to name it.
|
|
698
|
+
has to name it. Refused below edit.
|
|
646
699
|
"""
|
|
700
|
+
if not context.can_edit():
|
|
701
|
+
ui.notify("Saving needs edit access", type="warning", position="top-right")
|
|
702
|
+
return
|
|
647
703
|
active = self._get_active_haystack_name(context)
|
|
648
704
|
if not active:
|
|
649
705
|
self._on_save_haystack(context)
|
|
@@ -667,6 +723,8 @@ class HaystackEditor(BaseEditor):
|
|
|
667
723
|
|
|
668
724
|
def _on_load_haystack(self, context: "SessionContext") -> None:
|
|
669
725
|
"""Load a haystack, replacing all currently open graphs."""
|
|
726
|
+
if _refused(context, AccessTier.EDIT, "Loading a haystack"):
|
|
727
|
+
return
|
|
670
728
|
app: IAppState = context.app
|
|
671
729
|
if app is None:
|
|
672
730
|
ui.notify("Graph manager not available", type="warning")
|
|
@@ -731,6 +789,8 @@ class HaystackEditor(BaseEditor):
|
|
|
731
789
|
a GraphEditor tab. The user opens it in an editor by selecting the
|
|
732
790
|
new entry from the list once it appears.
|
|
733
791
|
"""
|
|
792
|
+
if _refused(context, AccessTier.EDIT, "Loading a graph"):
|
|
793
|
+
return
|
|
734
794
|
app = context.app
|
|
735
795
|
if app is None:
|
|
736
796
|
ui.notify("Graph manager not available", type="warning")
|
|
@@ -790,6 +850,8 @@ class HaystackEditor(BaseEditor):
|
|
|
790
850
|
|
|
791
851
|
def _on_rename_haystack(self, context: "SessionContext") -> None:
|
|
792
852
|
"""Open a modal to rename the current haystack."""
|
|
853
|
+
if _refused(context, AccessTier.EDIT, "Renaming"):
|
|
854
|
+
return
|
|
793
855
|
old_name = self._get_active_haystack_name(context)
|
|
794
856
|
if not old_name:
|
|
795
857
|
ui.notify("No haystack is currently loaded", type="warning")
|
|
@@ -59,7 +59,7 @@ def _compile_row(result) -> dict:
|
|
|
59
59
|
"to pass to haystack_open_graph.",
|
|
60
60
|
registry_id="list_graphs",
|
|
61
61
|
annotations=_READ_ONLY,
|
|
62
|
-
|
|
62
|
+
requires=AccessTier.VIEW,
|
|
63
63
|
)
|
|
64
64
|
class HaystackListGraphsTool(Farmhand):
|
|
65
65
|
async def run(self, ctx: FarmhandContext, limit: int = 100, offset: int = 0) -> dict:
|
|
@@ -101,7 +101,6 @@ class HaystackListGraphsTool(Farmhand):
|
|
|
101
101
|
"graph_editor_add_node, then haystack_save_graph (it stays unsaved until you do).",
|
|
102
102
|
registry_id="create_graph",
|
|
103
103
|
annotations=_MUTATING,
|
|
104
|
-
access=AccessTier.EDIT,
|
|
105
104
|
)
|
|
106
105
|
class HaystackCreateGraphTool(Farmhand):
|
|
107
106
|
async def run(self, ctx: FarmhandContext) -> dict:
|
|
@@ -126,7 +125,6 @@ class HaystackCreateGraphTool(Farmhand):
|
|
|
126
125
|
"to see valid paths.",
|
|
127
126
|
registry_id="open_graph",
|
|
128
127
|
annotations=_MUTATING,
|
|
129
|
-
access=AccessTier.EDIT,
|
|
130
128
|
)
|
|
131
129
|
class HaystackOpenGraphTool(Farmhand):
|
|
132
130
|
async def run(self, ctx: FarmhandContext, path: str) -> dict:
|
|
@@ -151,7 +149,6 @@ class HaystackOpenGraphTool(Farmhand):
|
|
|
151
149
|
"binding_id, save_failed if the write itself fails.",
|
|
152
150
|
registry_id="save_graph",
|
|
153
151
|
annotations=_MUTATING,
|
|
154
|
-
access=AccessTier.EDIT,
|
|
155
152
|
)
|
|
156
153
|
class HaystackSaveGraphTool(Farmhand):
|
|
157
154
|
async def run(self, ctx: FarmhandContext, binding_id: str, save_as: str | None = None) -> dict:
|
|
@@ -175,7 +172,6 @@ class HaystackSaveGraphTool(Farmhand):
|
|
|
175
172
|
"binding_id, rename_failed if the rename itself fails (e.g. name collision).",
|
|
176
173
|
registry_id="rename_graph",
|
|
177
174
|
annotations=_MUTATING,
|
|
178
|
-
access=AccessTier.EDIT,
|
|
179
175
|
)
|
|
180
176
|
class HaystackRenameGraphTool(Farmhand):
|
|
181
177
|
async def run(self, ctx: FarmhandContext, binding_id: str, new_name: str) -> dict:
|
|
@@ -196,7 +192,6 @@ class HaystackRenameGraphTool(Farmhand):
|
|
|
196
192
|
"haystack_open_graph. Raises graph_not_found for an unknown binding_id.",
|
|
197
193
|
registry_id="close_graph",
|
|
198
194
|
annotations=_MUTATING,
|
|
199
|
-
access=AccessTier.EDIT,
|
|
200
195
|
)
|
|
201
196
|
class HaystackCloseGraphTool(Farmhand):
|
|
202
197
|
async def run(self, ctx: FarmhandContext, binding_id: str) -> dict:
|
|
@@ -214,7 +209,7 @@ class HaystackCloseGraphTool(Farmhand):
|
|
|
214
209
|
"when ok).",
|
|
215
210
|
registry_id="compile_graph",
|
|
216
211
|
annotations=_READ_ONLY,
|
|
217
|
-
|
|
212
|
+
requires=AccessTier.VIEW,
|
|
218
213
|
)
|
|
219
214
|
class HaystackCompileGraphTool(Farmhand):
|
|
220
215
|
async def run(self, ctx: FarmhandContext, binding_id: str) -> dict:
|
|
@@ -231,7 +226,7 @@ class HaystackCompileGraphTool(Farmhand):
|
|
|
231
226
|
"haystack_stop_graph when done.",
|
|
232
227
|
registry_id="start_graph",
|
|
233
228
|
annotations=ToolAnnotations(destructive_hint=True),
|
|
234
|
-
|
|
229
|
+
requires=AccessTier.OPERATE,
|
|
235
230
|
)
|
|
236
231
|
class HaystackStartGraphTool(Farmhand):
|
|
237
232
|
async def run(self, ctx: FarmhandContext, binding_id: str) -> dict:
|
|
@@ -253,7 +248,7 @@ class HaystackStartGraphTool(Farmhand):
|
|
|
253
248
|
"currently running.",
|
|
254
249
|
registry_id="stop_graph",
|
|
255
250
|
annotations=_MUTATING,
|
|
256
|
-
|
|
251
|
+
requires=AccessTier.OPERATE,
|
|
257
252
|
)
|
|
258
253
|
class HaystackStopGraphTool(Farmhand):
|
|
259
254
|
async def run(self, ctx: FarmhandContext, binding_id: str) -> dict:
|
|
@@ -67,6 +67,11 @@ class GraphEntry(GraphContainer):
|
|
|
67
67
|
persisted in the haystack TOML under ``[graphs.run]``.
|
|
68
68
|
kind: Which category this entry belongs to. See
|
|
69
69
|
:class:`EntryKind`.
|
|
70
|
+
last_undo_availability: ``(can_undo, can_redo, undo_redo_count)`` as
|
|
71
|
+
of the last validation result that changed saved state;
|
|
72
|
+
``None`` before the first. Lets the validation handler
|
|
73
|
+
broadcast only when availability changes, counting an
|
|
74
|
+
undo or redo in between as a change.
|
|
70
75
|
"""
|
|
71
76
|
|
|
72
77
|
graph: "HaywireGraph"
|
|
@@ -77,6 +82,9 @@ class GraphEntry(GraphContainer):
|
|
|
77
82
|
haystack: "Optional[HaystackState]" = field(default=None, repr=False)
|
|
78
83
|
run_settings: GraphRunSettings = field(default_factory=GraphRunSettings)
|
|
79
84
|
kind: EntryKind = EntryKind.GRAPH
|
|
85
|
+
last_undo_availability: Optional[tuple[bool, bool, int]] = field(default=None, repr=False)
|
|
86
|
+
_abandoned: list["Interpreter"] = field(default_factory=list, init=False, repr=False)
|
|
87
|
+
"""Stopped Interpreters whose runner threads are still stuck in a node."""
|
|
80
88
|
|
|
81
89
|
@property
|
|
82
90
|
def binding_id(self) -> str:
|
|
@@ -119,6 +127,8 @@ class GraphEntry(GraphContainer):
|
|
|
119
127
|
Returns a failed result for an entry whose :attr:`kind` is not
|
|
120
128
|
executable, naming the reason: a macro document holds no EVENT node, so
|
|
121
129
|
assembly would find nothing to run and the verdict would not say why.
|
|
130
|
+
Also refuses while a previous run of this graph is still stuck in a
|
|
131
|
+
node (its runner was abandoned by a stop).
|
|
122
132
|
"""
|
|
123
133
|
if not self.kind.is_executable():
|
|
124
134
|
return CompileResult(
|
|
@@ -131,6 +141,16 @@ class GraphEntry(GraphContainer):
|
|
|
131
141
|
if self.is_executing:
|
|
132
142
|
return CompileResult(ok=True, error=None)
|
|
133
143
|
|
|
144
|
+
self._abandoned = [interp for interp in self._abandoned if interp.has_abandoned_runners()]
|
|
145
|
+
if self._abandoned:
|
|
146
|
+
return CompileResult(
|
|
147
|
+
ok=False,
|
|
148
|
+
error=(
|
|
149
|
+
f"a previous run of '{self.display_name}' is still stuck in a node; "
|
|
150
|
+
"wait for it to finish or restart the studio"
|
|
151
|
+
),
|
|
152
|
+
)
|
|
153
|
+
|
|
134
154
|
library_state_container = get_library_state_container()
|
|
135
155
|
interpreter = Interpreter(library_state_container=library_state_container)
|
|
136
156
|
try:
|
|
@@ -169,9 +189,14 @@ class GraphEntry(GraphContainer):
|
|
|
169
189
|
self.interpreter.stop_execution()
|
|
170
190
|
except Exception as e:
|
|
171
191
|
logger.warning(f"Error stopping execution on '{self.display_name}': {e}")
|
|
172
|
-
self.
|
|
192
|
+
self._retire_interpreter()
|
|
173
193
|
logger.info(f"Execution stopped for graph '{self.display_name}'")
|
|
174
194
|
|
|
195
|
+
def _retire_interpreter(self) -> None:
|
|
196
|
+
interpreter, self.interpreter = self.interpreter, None
|
|
197
|
+
if interpreter is not None and interpreter.has_abandoned_runners():
|
|
198
|
+
self._abandoned.append(interpreter)
|
|
199
|
+
|
|
175
200
|
def save(self, save_as: "Optional[Path]" = None) -> "Optional[str]":
|
|
176
201
|
"""Persist this entry via its owning HaystackState.
|
|
177
202
|
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
from haybale_haystack.panels.file_browser.menu.file import OpenInHaystackMenuPanel
|
|
2
|
+
from haybale_haystack.panels.properties.introspect.graph_run_settings_panel import GraphRunSettingsPanel
|
|
3
|
+
|
|
4
|
+
# Backwards-compat alias
|
|
5
|
+
OpenInHaystackPanel = OpenInHaystackMenuPanel
|
|
6
|
+
|
|
7
|
+
__all__ = [
|
|
8
|
+
"GraphRunSettingsPanel",
|
|
9
|
+
"OpenInHaystackMenuPanel",
|
|
10
|
+
"OpenInHaystackPanel",
|
|
11
|
+
]
|