darabos commited on
Commit
64d244a
·
1 Parent(s): 44a2d59

Tri-state status. Nicer visuals.

Browse files
lynxkite-app/src/lynxkite_app/crdt.py CHANGED
@@ -86,7 +86,7 @@ def clean_input(ws_pyd):
86
  for node in ws_pyd.nodes:
87
  node.data.display = None
88
  node.data.error = None
89
- node.data.in_progress = False
90
  node.position.x = 0
91
  node.position.y = 0
92
  if node.model_extra:
@@ -228,7 +228,7 @@ async def execute(
228
  for nc, np in zip(ws_crdt["nodes"], ws_pyd.nodes):
229
  if "data" not in nc:
230
  nc["data"] = pycrdt.Map()
231
- nc["data"]["in_progress"] = True
232
  # Nodes get a reference to their CRDT maps, so they can update them as the results come in.
233
  np._crdt = nc
234
  await workspace.execute(ws_pyd)
 
86
  for node in ws_pyd.nodes:
87
  node.data.display = None
88
  node.data.error = None
89
+ node.data.status = workspace.NodeStatus.done
90
  node.position.x = 0
91
  node.position.y = 0
92
  if node.model_extra:
 
228
  for nc, np in zip(ws_crdt["nodes"], ws_pyd.nodes):
229
  if "data" not in nc:
230
  nc["data"] = pycrdt.Map()
231
+ nc["data"]["status"] = "planned"
232
  # Nodes get a reference to their CRDT maps, so they can update them as the results come in.
233
  np._crdt = nc
234
  await workspace.execute(ws_pyd)
lynxkite-app/web/src/index.css CHANGED
@@ -94,8 +94,19 @@ body {
94
  padding: 8px;
95
  }
96
 
97
- .lynxkite-node .title.in-progress {
98
- background: yellow;
 
 
 
 
 
 
 
 
 
 
 
99
  }
100
 
101
  .handle-name {
@@ -325,6 +336,20 @@ body {
325
  }
326
  }
327
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
328
  .react-flow__edge.selected path.react-flow__edge-path {
329
  outline: var(--xy-selection-border, var(--xy-selection-border-default));
330
  outline-offset: 10px;
 
94
  padding: 8px;
95
  }
96
 
97
+ .lynxkite-node .title.active {
98
+ background: linear-gradient(
99
+ to right,
100
+ oklch(75% 0.2 55),
101
+ oklch(90% 0.2 55),
102
+ oklch(75% 0.1 55)
103
+ );
104
+ background-size: 180% 180%;
105
+ animation: active-node-gradient-animation 2s ease-in-out infinite;
106
+ }
107
+
108
+ .lynxkite-node .title.planned {
109
+ background: oklch(75% 0.1 55);
110
  }
111
 
112
  .handle-name {
 
336
  }
337
  }
338
 
339
+ @keyframes active-node-gradient-animation {
340
+ 0% {
341
+ background-position-x: 100%;
342
+ }
343
+
344
+ 50% {
345
+ background-position-x: 0%;
346
+ }
347
+
348
+ 100% {
349
+ background-position-x: 100%;
350
+ }
351
+ }
352
+
353
  .react-flow__edge.selected path.react-flow__edge-path {
354
  outline: var(--xy-selection-border, var(--xy-selection-border-default));
355
  outline-offset: 10px;
lynxkite-app/web/src/workspace/nodes/LynxKiteNode.tsx CHANGED
@@ -72,7 +72,7 @@ export default function LynxKiteNode(props: LynxKiteNodeProps) {
72
  >
73
  <div className="lynxkite-node" style={props.nodeStyle}>
74
  <div
75
- className={`title bg-primary ${data.in_progress ? "in-progress" : ""}`}
76
  onClick={titleClicked}
77
  >
78
  {data.title}
 
72
  >
73
  <div className="lynxkite-node" style={props.nodeStyle}>
74
  <div
75
+ className={`title bg-primary ${data.status}`}
76
  onClick={titleClicked}
77
  >
78
  {data.title}
lynxkite-core/src/lynxkite/core/workspace.py CHANGED
@@ -3,6 +3,7 @@
3
  import json
4
  from typing import Optional
5
  import dataclasses
 
6
  import os
7
  import pycrdt
8
  import pydantic
@@ -21,12 +22,18 @@ class Position(BaseConfig):
21
  y: float
22
 
23
 
 
 
 
 
 
 
24
  class WorkspaceNodeData(BaseConfig):
25
  title: str
26
  params: dict
27
  display: Optional[object] = None
28
  error: Optional[str] = None
29
- in_progress: bool = False
30
  # Also contains a "meta" field when going out.
31
  # This is ignored when coming back from the frontend.
32
 
@@ -40,16 +47,22 @@ class WorkspaceNode(BaseConfig):
40
  position: Position
41
  _crdt: pycrdt.Map
42
 
 
 
 
 
 
 
43
  def publish_result(self, result: ops.Result):
44
  """Sends the result to the frontend. Call this in an executor when the result is available."""
45
  self.data.display = result.display
46
  self.data.error = result.error
47
- self.data.in_progress = False
48
  if hasattr(self, "_crdt"):
49
  with self._crdt.doc.transaction():
50
  self._crdt["data"]["display"] = result.display
51
  self._crdt["data"]["error"] = result.error
52
- self._crdt["data"]["in_progress"] = False
53
 
54
  def publish_error(self, error: Exception | str):
55
  self.publish_result(ops.Result(error=str(error)))
 
3
  import json
4
  from typing import Optional
5
  import dataclasses
6
+ import enum
7
  import os
8
  import pycrdt
9
  import pydantic
 
22
  y: float
23
 
24
 
25
+ class NodeStatus(str, enum.Enum):
26
+ planned = "planned"
27
+ active = "active"
28
+ done = "done"
29
+
30
+
31
  class WorkspaceNodeData(BaseConfig):
32
  title: str
33
  params: dict
34
  display: Optional[object] = None
35
  error: Optional[str] = None
36
+ status: NodeStatus = NodeStatus.done
37
  # Also contains a "meta" field when going out.
38
  # This is ignored when coming back from the frontend.
39
 
 
47
  position: Position
48
  _crdt: pycrdt.Map
49
 
50
+ def publish_started(self):
51
+ """Notifies the frontend that work has started on this node."""
52
+ self.data.status = NodeStatus.active
53
+ if hasattr(self, "_crdt"):
54
+ self._crdt["data"]["status"] = NodeStatus.active
55
+
56
  def publish_result(self, result: ops.Result):
57
  """Sends the result to the frontend. Call this in an executor when the result is available."""
58
  self.data.display = result.display
59
  self.data.error = result.error
60
+ self.data.status = NodeStatus.done
61
  if hasattr(self, "_crdt"):
62
  with self._crdt.doc.transaction():
63
  self._crdt["data"]["display"] = result.display
64
  self._crdt["data"]["error"] = result.error
65
+ self._crdt["data"]["status"] = NodeStatus.done
66
 
67
  def publish_error(self, error: Exception | str):
68
  self.publish_result(ops.Result(error=str(error)))
lynxkite-graph-analytics/src/lynxkite_graph_analytics/core.py CHANGED
@@ -164,6 +164,7 @@ async def execute(ws):
164
  node.publish_error("Operation not found in catalog")
165
  failed += 1
166
  continue
 
167
  try:
168
  # Convert inputs types to match operation signature.
169
  for i, (x, p) in enumerate(zip(inputs, op.inputs.values())):
 
164
  node.publish_error("Operation not found in catalog")
165
  failed += 1
166
  continue
167
+ node.publish_started()
168
  try:
169
  # Convert inputs types to match operation signature.
170
  for i, (x, p) in enumerate(zip(inputs, op.inputs.values())):