haybale-haystack 0.2.4__tar.gz → 0.3.1__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.1}/.gitignore +3 -1
- {haybale_haystack-0.2.4 → haybale_haystack-0.3.1}/PKG-INFO +5 -5
- {haybale_haystack-0.2.4 → haybale_haystack-0.3.1}/README.md +6 -5
- {haybale_haystack-0.2.4 → haybale_haystack-0.3.1}/haybale_haystack/OVERVIEW.md +6 -5
- {haybale_haystack-0.2.4 → haybale_haystack-0.3.1}/haybale_haystack/QUICKREF.md +7 -6
- haybale_haystack-0.3.1/haybale_haystack/docs/haybale-haystack.panel.EditMacroMenuPanel.md +18 -0
- {haybale_haystack-0.2.4 → haybale_haystack-0.3.1}/haybale_haystack/docs/haybale-haystack.panel.GraphRunSettingsPanel.md +2 -0
- {haybale_haystack-0.2.4 → haybale_haystack-0.3.1}/haybale_haystack/docs/haybale-haystack.panel.OpenInHaystackMenuPanel.md +2 -0
- haybale_haystack-0.3.1/haybale_haystack/docs/haybale-haystack.panel.UpdateMacroMenuPanel.md +21 -0
- {haybale_haystack-0.2.4 → haybale_haystack-0.3.1}/haybale_haystack/docs/haybale-haystack.setting.HaystackSettings.md +2 -0
- {haybale_haystack-0.2.4 → haybale_haystack-0.3.1}/haybale_haystack/docs/haybale-haystack.state.HaystackState.md +2 -0
- {haybale_haystack-0.2.4 → haybale_haystack-0.3.1}/haybale_haystack/editors/haystack_editor.py +180 -25
- {haybale_haystack-0.2.4 → haybale_haystack-0.3.1}/haybale_haystack/farmhands/graph_tools.py +4 -9
- {haybale_haystack-0.2.4 → haybale_haystack-0.3.1}/haybale_haystack/graph_entry.py +40 -2
- {haybale_haystack-0.2.4 → haybale_haystack-0.3.1}/haybale_haystack/haybale.toml +1 -1
- haybale_haystack-0.3.1/haybale_haystack/panels/__init__.py +11 -0
- {haybale_haystack-0.2.4 → haybale_haystack-0.3.1}/haybale_haystack/panels/file_browser/menu/file.py +1 -0
- haybale_haystack-0.3.1/haybale_haystack/panels/graph/menu/macro.py +269 -0
- {haybale_haystack-0.2.4 → haybale_haystack-0.3.1}/haybale_haystack/panels/properties/introspect/graph_run_settings_panel.py +2 -1
- haybale_haystack-0.3.1/haybale_haystack/settings/graph_run_settings.py +27 -0
- {haybale_haystack-0.2.4 → haybale_haystack-0.3.1}/haybale_haystack/settings/haystack_settings.py +1 -1
- {haybale_haystack-0.2.4 → haybale_haystack-0.3.1}/haybale_haystack/state/haystack_state.py +137 -21
- {haybale_haystack-0.2.4 → haybale_haystack-0.3.1}/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/settings/graph_run_settings.py +0 -16
- {haybale_haystack-0.2.4 → haybale_haystack-0.3.1}/haybale_haystack/__init__.py +0 -0
- {haybale_haystack-0.2.4 → haybale_haystack-0.3.1}/haybale_haystack/docs/haybale-haystack.editor.HaystackEditor.md +0 -0
- {haybale_haystack-0.2.4 → haybale_haystack-0.3.1}/haybale_haystack/docs/haybale-haystack.farmhand.close_graph.md +0 -0
- {haybale_haystack-0.2.4 → haybale_haystack-0.3.1}/haybale_haystack/docs/haybale-haystack.farmhand.compile_graph.md +0 -0
- {haybale_haystack-0.2.4 → haybale_haystack-0.3.1}/haybale_haystack/docs/haybale-haystack.farmhand.create_graph.md +0 -0
- {haybale_haystack-0.2.4 → haybale_haystack-0.3.1}/haybale_haystack/docs/haybale-haystack.farmhand.list_graphs.md +0 -0
- {haybale_haystack-0.2.4 → haybale_haystack-0.3.1}/haybale_haystack/docs/haybale-haystack.farmhand.open_graph.md +0 -0
- {haybale_haystack-0.2.4 → haybale_haystack-0.3.1}/haybale_haystack/docs/haybale-haystack.farmhand.rename_graph.md +0 -0
- {haybale_haystack-0.2.4 → haybale_haystack-0.3.1}/haybale_haystack/docs/haybale-haystack.farmhand.save_graph.md +0 -0
- {haybale_haystack-0.2.4 → haybale_haystack-0.3.1}/haybale_haystack/docs/haybale-haystack.farmhand.start_graph.md +0 -0
- {haybale_haystack-0.2.4 → haybale_haystack-0.3.1}/haybale_haystack/docs/haybale-haystack.farmhand.stop_graph.md +0 -0
- {haybale_haystack-0.2.4 → haybale_haystack-0.3.1}/haybale_haystack/editors/__init__.py +0 -0
- {haybale_haystack-0.2.4 → haybale_haystack-0.3.1}/haybale_haystack/farmhands/__init__.py +0 -0
- {haybale_haystack-0.2.4 → haybale_haystack-0.3.1}/haybale_haystack/persistence.py +0 -0
- {haybale_haystack-0.2.4 → haybale_haystack-0.3.1}/haybale_haystack/settings/__init__.py +0 -0
- {haybale_haystack-0.2.4 → haybale_haystack-0.3.1}/haybale_haystack/signals.py +0 -0
- {haybale_haystack-0.2.4 → haybale_haystack-0.3.1}/haybale_haystack/state/__init__.py +0 -0
|
@@ -220,6 +220,8 @@ packages/haywire-core/src/haywire/_baked_docs/*
|
|
|
220
220
|
# Haywire project config (user-local, not repo state)
|
|
221
221
|
.haywire/
|
|
222
222
|
|
|
223
|
-
# Local
|
|
223
|
+
# Local symlinks to external dev projects (machine-specific paths)
|
|
224
224
|
barn/haybale-visiongraph
|
|
225
|
+
barn/haybale-spandrel
|
|
225
226
|
|
|
227
|
+
assets/*
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: haybale-haystack
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.1
|
|
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.1
|
|
9
|
+
Requires-Dist: haybale-studio>=0.3.1
|
|
10
|
+
Requires-Dist: haywire-core>=0.3.1
|
|
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.1)
|
|
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.1}/haybale_haystack/editors/haystack_editor.py
RENAMED
|
@@ -6,7 +6,9 @@ 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
|
|
11
|
+
from haywire.ui.components.popup import Popup
|
|
10
12
|
from haywire.ui.editor import BaseEditor, SlotName, editor
|
|
11
13
|
from haywire.core.session import (
|
|
12
14
|
IAppState,
|
|
@@ -19,29 +21,41 @@ from haywire.core.signals import (
|
|
|
19
21
|
BroadcastClose,
|
|
20
22
|
Close,
|
|
21
23
|
GraphDataMutated,
|
|
24
|
+
GraphUnsavedChanged,
|
|
22
25
|
Reveal,
|
|
23
26
|
RevealGraphInstance,
|
|
27
|
+
Save,
|
|
24
28
|
)
|
|
25
29
|
from haywire.ui.modals import confirm_modal, pick_modal, rename_modal
|
|
26
30
|
|
|
31
|
+
from haybale_graph_editor.notices import GraphNotice, GraphNoticesChanged
|
|
27
32
|
from haybale_graph_editor.state.edit_state import EditState
|
|
28
33
|
from haybale_graph_editor.state.graph_app_state import GraphAppState
|
|
29
|
-
from haybale_graph_editor.editors.graph_editor import GraphEditor
|
|
34
|
+
from haybale_graph_editor.editors.graph_editor import GraphEditor, definition_path
|
|
30
35
|
from haybale_graph_editor.editors.graph_save_as import open_graph_save_as_dialog
|
|
31
36
|
|
|
32
|
-
from
|
|
33
|
-
from
|
|
34
|
-
from
|
|
37
|
+
from haybale_haystack.signals import HaystackReloaded, HaystackTeardown
|
|
38
|
+
from haybale_haystack.state.haystack_state import HaystackState
|
|
39
|
+
from haybale_haystack.graph_entry import EntryKind, GraphEntry
|
|
35
40
|
|
|
36
41
|
logger = logging.getLogger(__name__)
|
|
37
42
|
|
|
38
43
|
|
|
44
|
+
def _refused(context: "SessionContext", required: AccessTier, action: str) -> bool:
|
|
45
|
+
"""Return whether the principal lacks ``required``, telling them ``"<action> needs <tier> access"``."""
|
|
46
|
+
if context.can_access(required):
|
|
47
|
+
return False
|
|
48
|
+
ui.notify(f"{action} needs {required.value} access", type="warning", position="top-right")
|
|
49
|
+
return True
|
|
50
|
+
|
|
51
|
+
|
|
39
52
|
@editor(
|
|
40
53
|
label="Haystack",
|
|
41
54
|
icon=hui.icon.haystack,
|
|
42
55
|
default_slot=SlotName.ACTION,
|
|
43
56
|
description='All open graphs. Click to switch; "+" to create a new graph.',
|
|
44
57
|
order=20,
|
|
58
|
+
requires=AccessTier.VIEW,
|
|
45
59
|
)
|
|
46
60
|
class HaystackEditor(BaseEditor):
|
|
47
61
|
"""
|
|
@@ -63,12 +77,16 @@ class HaystackEditor(BaseEditor):
|
|
|
63
77
|
def __init__(self, wrapper):
|
|
64
78
|
super().__init__(wrapper)
|
|
65
79
|
self._list_container = None
|
|
80
|
+
self._dismissed_notices: set[tuple[str, str]] = set()
|
|
81
|
+
self._notice_popups: list[Popup] = []
|
|
66
82
|
|
|
67
83
|
# ------------------------------------------------------------------
|
|
68
84
|
# Signal-bus subscriptions / draw
|
|
69
85
|
# ------------------------------------------------------------------
|
|
70
86
|
|
|
71
|
-
@redraw_on(
|
|
87
|
+
@redraw_on(
|
|
88
|
+
ActiveGraphMoved, GraphDataMutated, GraphUnsavedChanged, HaystackReloaded, GraphNoticesChanged
|
|
89
|
+
)
|
|
72
90
|
def _refresh_on_state_move(self, context: "SessionContext", event) -> None:
|
|
73
91
|
"""Empty body — the decorator triggers ``wrapper.redraw()`` after
|
|
74
92
|
this returns, which re-runs ``draw`` and ``_render_list``."""
|
|
@@ -85,25 +103,36 @@ class HaystackEditor(BaseEditor):
|
|
|
85
103
|
"""
|
|
86
104
|
self._on_haystack_teardown(context, event)
|
|
87
105
|
|
|
106
|
+
@react_on(Save)
|
|
107
|
+
def _on_save(self, context: "SessionContext", event: Save) -> None:
|
|
108
|
+
"""Save the loaded haystack in place, or ask for a name when none is loaded."""
|
|
109
|
+
self._on_save_haystack_in_place(context)
|
|
110
|
+
|
|
88
111
|
@react_on(RevealGraphInstance)
|
|
89
112
|
def _on_reveal_graph_instance_reopen(
|
|
90
113
|
self, context: "SessionContext", event: "RevealGraphInstance"
|
|
91
114
|
) -> None:
|
|
92
|
-
"""
|
|
115
|
+
"""Bring forward the tab of the document holding ``event.graph_id``, opening it if needed.
|
|
93
116
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
node/edge in the freshly-constructed GraphEditor, which was not
|
|
98
|
-
alive to receive the original signal.
|
|
117
|
+
The graph may be the document's own or a Group or macro placement interior
|
|
118
|
+
in it, open or not. A tab this opens carries out the reveal when it draws
|
|
119
|
+
(``EditState.pending_reveal``); a tab already open carries it out itself.
|
|
99
120
|
"""
|
|
100
121
|
graph_app_state = context.app_data[GraphAppState]
|
|
101
122
|
for container in graph_app_state.all_containers():
|
|
102
|
-
|
|
123
|
+
graph = container.editor.graph
|
|
124
|
+
if graph.graph_id != event.graph_id and definition_path(graph, event.graph_id) is None:
|
|
125
|
+
continue
|
|
126
|
+
edit_state = context.data[EditState]
|
|
127
|
+
edit_state.pending_reveal = event
|
|
128
|
+
try:
|
|
103
129
|
context.session.publish(
|
|
104
130
|
Reveal(editor=GraphEditor, binding_id=container.binding_id, label=container.display_name)
|
|
105
131
|
)
|
|
106
|
-
|
|
132
|
+
finally:
|
|
133
|
+
# A tab already open draws nothing, so the reveal must not wait for a later draw.
|
|
134
|
+
edit_state.pending_reveal = None
|
|
135
|
+
return
|
|
107
136
|
|
|
108
137
|
def _on_haystack_teardown(self, context: "SessionContext", signal: "HaystackTeardown") -> None:
|
|
109
138
|
"""Translate the teardown fact into local tab-close lifecycle commands.
|
|
@@ -129,7 +158,7 @@ class HaystackEditor(BaseEditor):
|
|
|
129
158
|
|
|
130
159
|
# No on_focus override needed: @redraw_on fires regardless of active-tab
|
|
131
160
|
# state, so backgrounded HaystackEditor instances stay current with
|
|
132
|
-
# ActiveGraphMoved / GraphDataMutated / HaystackReloaded.
|
|
161
|
+
# ActiveGraphMoved / GraphDataMutated / GraphUnsavedChanged / HaystackReloaded.
|
|
133
162
|
|
|
134
163
|
def draw(self, context: "SessionContext", container: ui.element) -> None:
|
|
135
164
|
with container:
|
|
@@ -144,13 +173,16 @@ class HaystackEditor(BaseEditor):
|
|
|
144
173
|
# ------------------------------------------------------------------
|
|
145
174
|
|
|
146
175
|
def _render_header(self, context: "SessionContext") -> None:
|
|
176
|
+
"""Draw the title, unsaved dot and haystack actions; the actions are drawn only for edit."""
|
|
147
177
|
title = self._get_active_haystack_name(context) or "Haystacks"
|
|
148
178
|
hs = context.app_data[HaystackState]
|
|
149
179
|
is_dirty = hs.is_haystack_dirty
|
|
150
|
-
# The dirty dot (when is_dirty=True) is rendered by
|
|
180
|
+
# The dirty dot (when is_dirty=True) is rendered by slot_header
|
|
151
181
|
# itself between the icon and the title — we only add the save
|
|
152
182
|
# icon and the overflow menu on the action side here.
|
|
153
|
-
with hui.
|
|
183
|
+
with hui.slot_header(title, icon=hui.icon.haystack, is_dirty=is_dirty):
|
|
184
|
+
if not context.can_edit():
|
|
185
|
+
return
|
|
154
186
|
# Save icon — paired with the dirty dot; only when dirty.
|
|
155
187
|
if is_dirty:
|
|
156
188
|
hui.icon_action(
|
|
@@ -199,11 +231,12 @@ class HaystackEditor(BaseEditor):
|
|
|
199
231
|
if self._list_container is None:
|
|
200
232
|
return
|
|
201
233
|
self._list_container.clear()
|
|
234
|
+
self._delete_notice_popups()
|
|
202
235
|
|
|
203
236
|
app = context.app
|
|
204
237
|
if app is None:
|
|
205
238
|
with self._list_container:
|
|
206
|
-
|
|
239
|
+
hui.hint("Graph manager not available").classes("p-2 italic")
|
|
207
240
|
return
|
|
208
241
|
|
|
209
242
|
hs = context.app_data[HaystackState]
|
|
@@ -212,7 +245,7 @@ class HaystackEditor(BaseEditor):
|
|
|
212
245
|
|
|
213
246
|
with self._list_container:
|
|
214
247
|
if not graphs:
|
|
215
|
-
|
|
248
|
+
hui.hint("No graphs open").classes("p-2 italic")
|
|
216
249
|
else:
|
|
217
250
|
for entry in graphs:
|
|
218
251
|
self._render_entry(entry, context)
|
|
@@ -221,14 +254,17 @@ class HaystackEditor(BaseEditor):
|
|
|
221
254
|
# haystack and cannot be run, so it does not belong among the
|
|
222
255
|
# entries the play controls and the TOML apply to.
|
|
223
256
|
if macros:
|
|
224
|
-
|
|
257
|
+
hui.section_label("Macros")
|
|
225
258
|
for entry in macros:
|
|
226
259
|
self._render_entry(entry, context)
|
|
227
260
|
|
|
228
|
-
|
|
261
|
+
if context.can_edit():
|
|
262
|
+
self._render_add_bar(context)
|
|
229
263
|
|
|
230
264
|
def _render_entry(self, entry: "GraphEntry", context: "SessionContext") -> None:
|
|
231
265
|
is_active = entry.graph is context.data[EditState].active_graph
|
|
266
|
+
can_edit = context.can_edit()
|
|
267
|
+
can_operate = context.can_access(AccessTier.OPERATE)
|
|
232
268
|
is_unsaved = entry.unsaved or entry.path is None
|
|
233
269
|
is_executing = entry.is_executing
|
|
234
270
|
|
|
@@ -261,9 +297,12 @@ class HaystackEditor(BaseEditor):
|
|
|
261
297
|
# to run, so it carries its kind's icon instead of a control whose
|
|
262
298
|
# only answer would be a refusal.
|
|
263
299
|
if not entry.kind.is_executable():
|
|
264
|
-
ui.icon(hui.icon.
|
|
300
|
+
ui.icon(hui.icon.macro, size="16px").classes("hw-text-dim").tooltip(
|
|
265
301
|
"A macro is run by placing it in a graph"
|
|
266
302
|
)
|
|
303
|
+
elif not can_operate:
|
|
304
|
+
# The row's execution border still shows a running graph.
|
|
305
|
+
pass
|
|
267
306
|
elif is_executing:
|
|
268
307
|
hui.icon_action(
|
|
269
308
|
hui.icon.execution_stop,
|
|
@@ -277,6 +316,8 @@ class HaystackEditor(BaseEditor):
|
|
|
277
316
|
on_click=lambda eid=eid: self._on_start_execution(eid, context),
|
|
278
317
|
)
|
|
279
318
|
|
|
319
|
+
self._render_notices(eid, context)
|
|
320
|
+
|
|
280
321
|
# Dirty dot — amber when unsaved, hidden when clean
|
|
281
322
|
if is_unsaved:
|
|
282
323
|
ui.element("div").classes("w-2 h-2 rounded-full flex-shrink-0 bg-amber-400").style(
|
|
@@ -293,7 +334,7 @@ class HaystackEditor(BaseEditor):
|
|
|
293
334
|
ui.label("not saved").classes("text-xs hw-text-warning-dim")
|
|
294
335
|
|
|
295
336
|
# Save icon — paired with the dirty dot; only when unsaved.
|
|
296
|
-
if is_unsaved:
|
|
337
|
+
if is_unsaved and can_edit:
|
|
297
338
|
hui.icon_action(
|
|
298
339
|
hui.icon.save,
|
|
299
340
|
tooltip="Save",
|
|
@@ -301,7 +342,7 @@ class HaystackEditor(BaseEditor):
|
|
|
301
342
|
)
|
|
302
343
|
|
|
303
344
|
# Overflow menu — hidden during execution
|
|
304
|
-
if not is_executing:
|
|
345
|
+
if can_edit and not is_executing:
|
|
305
346
|
with (
|
|
306
347
|
ui.button(icon="more_vert")
|
|
307
348
|
.props("flat round dense size=xs")
|
|
@@ -332,6 +373,89 @@ class HaystackEditor(BaseEditor):
|
|
|
332
373
|
on_click=lambda eid=eid: self._on_entry_delete(eid, context),
|
|
333
374
|
)
|
|
334
375
|
|
|
376
|
+
# ------------------------------------------------------------------
|
|
377
|
+
# graph notices
|
|
378
|
+
# ------------------------------------------------------------------
|
|
379
|
+
|
|
380
|
+
def _delete_notice_popups(self) -> None:
|
|
381
|
+
for popup in self._notice_popups:
|
|
382
|
+
popup.delete()
|
|
383
|
+
self._notice_popups.clear()
|
|
384
|
+
|
|
385
|
+
def _visible_notices(self, binding_id: str, context: "SessionContext") -> list[GraphNotice]:
|
|
386
|
+
"""Return the notices on a graph that this editor has not dismissed."""
|
|
387
|
+
posted = list(context.app_data[GraphAppState].notices(binding_id))
|
|
388
|
+
return [n for n in posted if (binding_id, n.key) not in self._dismissed_notices]
|
|
389
|
+
|
|
390
|
+
def _render_notices(self, binding_id: str, context: "SessionContext") -> None:
|
|
391
|
+
"""Draw the notice icon, tinted warning, and the popup listing each notice and its action."""
|
|
392
|
+
notices = self._visible_notices(binding_id, context)
|
|
393
|
+
if not notices:
|
|
394
|
+
return
|
|
395
|
+
popup = Popup(
|
|
396
|
+
title="Notices", width="26rem", closable=True, backdrop_click_close=True, escape_close=True
|
|
397
|
+
)
|
|
398
|
+
self._notice_popups.append(popup)
|
|
399
|
+
with popup:
|
|
400
|
+
for notice in notices:
|
|
401
|
+
self._render_notice_line(binding_id, notice, popup, context)
|
|
402
|
+
icons = {n.icon for n in notices}
|
|
403
|
+
shared = icons.pop() if len(icons) == 1 else None
|
|
404
|
+
btn = ui.button().props("flat round dense size=xs data-hw-mark=graph-notice")
|
|
405
|
+
with btn:
|
|
406
|
+
# The icon carries the opt-out: the .hw-panel dim rule beats an inline colour.
|
|
407
|
+
ui.icon(shared or hui.icon.notice).classes("hw-use-props-color hw-text-warning")
|
|
408
|
+
btn.tooltip("\n".join(n.text for n in notices))
|
|
409
|
+
btn.on("click.stop", popup.open)
|
|
410
|
+
|
|
411
|
+
def _render_notice_line(
|
|
412
|
+
self, binding_id: str, notice: GraphNotice, popup: Popup, context: "SessionContext"
|
|
413
|
+
) -> None:
|
|
414
|
+
with (
|
|
415
|
+
ui.column()
|
|
416
|
+
.classes("w-full gap-1 p-2 rounded")
|
|
417
|
+
.style(
|
|
418
|
+
"border-left: 3px solid var(--hw-warning);"
|
|
419
|
+
" background: color-mix(in srgb, var(--hw-warning) 12%, transparent)"
|
|
420
|
+
)
|
|
421
|
+
):
|
|
422
|
+
with ui.row().classes("w-full items-start gap-2 no-wrap"):
|
|
423
|
+
ui.icon(notice.icon or hui.icon.notice, size="sm").classes(
|
|
424
|
+
"hw-use-props-color hw-text-warning"
|
|
425
|
+
).props("data-hw-mark=graph-notice-line-icon")
|
|
426
|
+
ui.label(notice.text).classes("flex-1 min-w-0 text-sm")
|
|
427
|
+
if notice.dismissable:
|
|
428
|
+
hui.icon_action(
|
|
429
|
+
hui.icon.close,
|
|
430
|
+
tooltip="Dismiss",
|
|
431
|
+
on_click=lambda k=notice.key: self._dismiss_notice(binding_id, k, context),
|
|
432
|
+
)
|
|
433
|
+
if notice.action is not None and notice.action_label and context.can_access(notice.requires):
|
|
434
|
+
with ui.row().classes("w-full justify-end"):
|
|
435
|
+
hui.button(
|
|
436
|
+
notice.action_label,
|
|
437
|
+
on_click=lambda n=notice: self._run_notice_action(n, popup, context),
|
|
438
|
+
).props("data-hw-mark=graph-notice-action").classes("w-auto")
|
|
439
|
+
|
|
440
|
+
def _run_notice_action(self, notice: GraphNotice, popup: Popup, context: "SessionContext") -> None:
|
|
441
|
+
"""Run a notice's action with this session's context, then close the popup."""
|
|
442
|
+
client = ui.context.client
|
|
443
|
+
popup.close()
|
|
444
|
+
if notice.action is None:
|
|
445
|
+
return
|
|
446
|
+
try:
|
|
447
|
+
notice.action(context)
|
|
448
|
+
except Exception as exc:
|
|
449
|
+
logger.warning(f"HaystackEditor: notice action {notice.key} failed: {exc}")
|
|
450
|
+
with client:
|
|
451
|
+
ui.notify(f"{notice.action_label or 'Action'} failed: {exc}", type="negative")
|
|
452
|
+
|
|
453
|
+
def _dismiss_notice(self, binding_id: str, key: str, context: "SessionContext") -> None:
|
|
454
|
+
"""Hide a notice in this editor instance only."""
|
|
455
|
+
self._dismissed_notices.add((binding_id, key))
|
|
456
|
+
# The list redraw deletes the popup (and this button's slot) holding the handler.
|
|
457
|
+
self._render_list(context)
|
|
458
|
+
|
|
335
459
|
# ------------------------------------------------------------------
|
|
336
460
|
# per-entry file actions
|
|
337
461
|
# ------------------------------------------------------------------
|
|
@@ -352,6 +476,9 @@ class HaystackEditor(BaseEditor):
|
|
|
352
476
|
|
|
353
477
|
def _on_entry_save(self, binding_id: str, context: "SessionContext") -> None:
|
|
354
478
|
"""Save a graph; opens save-as dialog if untitled."""
|
|
479
|
+
if not context.can_edit():
|
|
480
|
+
ui.notify("Saving needs edit access", type="warning", position="top-right")
|
|
481
|
+
return
|
|
355
482
|
app = context.app
|
|
356
483
|
if app is None:
|
|
357
484
|
ui.notify("Graph manager not available", type="warning")
|
|
@@ -393,6 +520,9 @@ class HaystackEditor(BaseEditor):
|
|
|
393
520
|
|
|
394
521
|
def _on_entry_save_as(self, binding_id: str, context: "SessionContext") -> None:
|
|
395
522
|
"""Always open the save-as dialog."""
|
|
523
|
+
if not context.can_edit():
|
|
524
|
+
ui.notify("Saving needs edit access", type="warning", position="top-right")
|
|
525
|
+
return
|
|
396
526
|
app = context.app
|
|
397
527
|
if app is None:
|
|
398
528
|
ui.notify("Graph manager not available", type="warning")
|
|
@@ -416,6 +546,8 @@ class HaystackEditor(BaseEditor):
|
|
|
416
546
|
|
|
417
547
|
def _on_entry_rename(self, binding_id: str, context: "SessionContext") -> None:
|
|
418
548
|
"""Rename: inline edit for file-backed graphs, save-as for untitled."""
|
|
549
|
+
if _refused(context, AccessTier.EDIT, "Renaming"):
|
|
550
|
+
return
|
|
419
551
|
entry = self._resolve_entry(binding_id, context)
|
|
420
552
|
if entry is None:
|
|
421
553
|
return
|
|
@@ -435,6 +567,8 @@ class HaystackEditor(BaseEditor):
|
|
|
435
567
|
confirmation that warns about losing unsaved work. Clean entries
|
|
436
568
|
get a milder confirm that calls out that the file stays on disk.
|
|
437
569
|
"""
|
|
570
|
+
if _refused(context, AccessTier.EDIT, "Removing"):
|
|
571
|
+
return
|
|
438
572
|
entry = self._resolve_entry(binding_id, context)
|
|
439
573
|
if entry is None:
|
|
440
574
|
return
|
|
@@ -498,6 +632,8 @@ class HaystackEditor(BaseEditor):
|
|
|
498
632
|
edit_state = context.data[EditState]
|
|
499
633
|
edit_state.active_graph = None
|
|
500
634
|
edit_state.active_graph_path = None
|
|
635
|
+
edit_state.active_route = None
|
|
636
|
+
context.level_mode = LevelMode.VIEW
|
|
501
637
|
if session:
|
|
502
638
|
session.publish(ActiveGraphMoved())
|
|
503
639
|
|
|
@@ -552,6 +688,8 @@ class HaystackEditor(BaseEditor):
|
|
|
552
688
|
|
|
553
689
|
def _on_new(self, context: "SessionContext") -> None:
|
|
554
690
|
"""Create a new unnamed graph and activate it."""
|
|
691
|
+
if _refused(context, AccessTier.EDIT, "A new graph"):
|
|
692
|
+
return
|
|
555
693
|
app: IAppState = context.app
|
|
556
694
|
session = context.session
|
|
557
695
|
if app is None or session is None:
|
|
@@ -589,6 +727,8 @@ class HaystackEditor(BaseEditor):
|
|
|
589
727
|
|
|
590
728
|
def _on_start_execution(self, binding_id: str, context: "SessionContext") -> None:
|
|
591
729
|
"""Start execution on a graph entry."""
|
|
730
|
+
if _refused(context, AccessTier.OPERATE, "Starting and stopping"):
|
|
731
|
+
return
|
|
592
732
|
entry = self._resolve_entry(binding_id, context)
|
|
593
733
|
if entry is None:
|
|
594
734
|
return
|
|
@@ -603,6 +743,8 @@ class HaystackEditor(BaseEditor):
|
|
|
603
743
|
|
|
604
744
|
def _on_stop_execution(self, binding_id: str, context: "SessionContext") -> None:
|
|
605
745
|
"""Stop execution on a graph entry."""
|
|
746
|
+
if _refused(context, AccessTier.OPERATE, "Starting and stopping"):
|
|
747
|
+
return
|
|
606
748
|
entry = self._resolve_entry(binding_id, context)
|
|
607
749
|
if entry is None:
|
|
608
750
|
return
|
|
@@ -615,7 +757,10 @@ class HaystackEditor(BaseEditor):
|
|
|
615
757
|
# ------------------------------------------------------------------
|
|
616
758
|
|
|
617
759
|
def _on_save_haystack(self, context: "SessionContext") -> None:
|
|
618
|
-
"""Save the current set of open graphs as a haystack ("Save As…")."""
|
|
760
|
+
"""Save the current set of open graphs as a haystack ("Save As…"); refused below edit."""
|
|
761
|
+
if not context.can_edit():
|
|
762
|
+
ui.notify("Saving needs edit access", type="warning", position="top-right")
|
|
763
|
+
return
|
|
619
764
|
app: IAppState = context.app
|
|
620
765
|
if app is None:
|
|
621
766
|
ui.notify("Graph manager not available", type="warning")
|
|
@@ -642,8 +787,11 @@ class HaystackEditor(BaseEditor):
|
|
|
642
787
|
|
|
643
788
|
Falls through to :meth:`_on_save_haystack` (the Save-As modal) when
|
|
644
789
|
no haystack is loaded — there's nothing to overwrite, so the user
|
|
645
|
-
has to name it.
|
|
790
|
+
has to name it. Refused below edit.
|
|
646
791
|
"""
|
|
792
|
+
if not context.can_edit():
|
|
793
|
+
ui.notify("Saving needs edit access", type="warning", position="top-right")
|
|
794
|
+
return
|
|
647
795
|
active = self._get_active_haystack_name(context)
|
|
648
796
|
if not active:
|
|
649
797
|
self._on_save_haystack(context)
|
|
@@ -667,6 +815,8 @@ class HaystackEditor(BaseEditor):
|
|
|
667
815
|
|
|
668
816
|
def _on_load_haystack(self, context: "SessionContext") -> None:
|
|
669
817
|
"""Load a haystack, replacing all currently open graphs."""
|
|
818
|
+
if _refused(context, AccessTier.EDIT, "Loading a haystack"):
|
|
819
|
+
return
|
|
670
820
|
app: IAppState = context.app
|
|
671
821
|
if app is None:
|
|
672
822
|
ui.notify("Graph manager not available", type="warning")
|
|
@@ -731,6 +881,8 @@ class HaystackEditor(BaseEditor):
|
|
|
731
881
|
a GraphEditor tab. The user opens it in an editor by selecting the
|
|
732
882
|
new entry from the list once it appears.
|
|
733
883
|
"""
|
|
884
|
+
if _refused(context, AccessTier.EDIT, "Loading a graph"):
|
|
885
|
+
return
|
|
734
886
|
app = context.app
|
|
735
887
|
if app is None:
|
|
736
888
|
ui.notify("Graph manager not available", type="warning")
|
|
@@ -790,6 +942,8 @@ class HaystackEditor(BaseEditor):
|
|
|
790
942
|
|
|
791
943
|
def _on_rename_haystack(self, context: "SessionContext") -> None:
|
|
792
944
|
"""Open a modal to rename the current haystack."""
|
|
945
|
+
if _refused(context, AccessTier.EDIT, "Renaming"):
|
|
946
|
+
return
|
|
793
947
|
old_name = self._get_active_haystack_name(context)
|
|
794
948
|
if not old_name:
|
|
795
949
|
ui.notify("No haystack is currently loaded", type="warning")
|
|
@@ -837,4 +991,5 @@ class HaystackEditor(BaseEditor):
|
|
|
837
991
|
# ------------------------------------------------------------------
|
|
838
992
|
|
|
839
993
|
def cleanup(self) -> None:
|
|
994
|
+
self._delete_notice_popups()
|
|
840
995
|
self._list_container = None
|