darabos commited on
Commit
91961e7
·
1 Parent(s): ef8a15c

Color picker for groups.

Browse files
lynxkite-app/web/src/common.ts CHANGED
@@ -5,3 +5,12 @@ export function usePath() {
5
  const path = decodeURIComponent(useLocation().pathname).replace(/[/]$/, "");
6
  return path;
7
  }
 
 
 
 
 
 
 
 
 
 
5
  const path = decodeURIComponent(useLocation().pathname).replace(/[/]$/, "");
6
  return path;
7
  }
8
+
9
+ export const COLORS: { [key: string]: string } = {
10
+ gray: "oklch(95% 0 0)",
11
+ pink: "oklch(75% 0.2 0)",
12
+ orange: "oklch(75% 0.2 55)",
13
+ green: "oklch(75% 0.2 150)",
14
+ blue: "oklch(75% 0.2 230)",
15
+ purple: "oklch(75% 0.2 290)",
16
+ };
lynxkite-app/web/src/index.css CHANGED
@@ -98,13 +98,30 @@ body {
98
  opacity: 1;
99
  }
100
 
101
- .react-flow__node-group,
102
- .react-flow__node-group.selectable.selected,
103
- .react-flow__node-group:hover {
104
  box-shadow: 0px 3px 30px 0px rgba(0, 0, 0, 0.3);
105
  border-radius: 20px;
106
  border: none;
107
- background-color: #fffb;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
108
  }
109
 
110
  .tooltip {
@@ -121,6 +138,10 @@ body {
121
  max-width: 300px;
122
  }
123
 
 
 
 
 
124
  .expanded .lynxkite-node {
125
  height: 100%;
126
  }
@@ -545,14 +566,14 @@ body {
545
  z-index: -10 !important;
546
  }
547
 
548
- .selected.selectable.react-flow__node-group,
549
  .selected .comment-view,
550
  .selected .lynxkite-node {
551
  outline: var(--xy-selection-border, var(--xy-selection-border-default));
552
  outline-offset: 7.5px;
553
  }
554
 
555
- .selected.selectable.react-flow__node-group {
556
  outline-offset: 20px;
557
  }
558
 
 
98
  opacity: 1;
99
  }
100
 
101
+ .node-group {
 
 
102
  box-shadow: 0px 3px 30px 0px rgba(0, 0, 0, 0.3);
103
  border-radius: 20px;
104
  border: none;
105
+ background-color: white;
106
+ opacity: 0.9;
107
+ display: flex;
108
+ flex-direction: column;
109
+ align-items: end;
110
+ padding: 10px 20px;
111
+ }
112
+
113
+ .node-group-color-picker-icon {
114
+ font-size: 30px;
115
+ opacity: 0.1;
116
+ transition: opacity 0.3s;
117
+ }
118
+
119
+ .node-group:hover .node-group-color-picker-icon {
120
+ opacity: 1;
121
+ }
122
+
123
+ .color-picker-button {
124
+ font-size: 30px;
125
  }
126
 
127
  .tooltip {
 
138
  max-width: 300px;
139
  }
140
 
141
+ .prose p {
142
+ margin-bottom: 0;
143
+ }
144
+
145
  .expanded .lynxkite-node {
146
  height: 100%;
147
  }
 
566
  z-index: -10 !important;
567
  }
568
 
569
+ .selected .node-group,
570
  .selected .comment-view,
571
  .selected .lynxkite-node {
572
  outline: var(--xy-selection-border, var(--xy-selection-border-default));
573
  outline-offset: 7.5px;
574
  }
575
 
576
+ .selected .node-group {
577
  outline-offset: 20px;
578
  }
579
 
lynxkite-app/web/src/workspace/Workspace.tsx CHANGED
@@ -42,6 +42,7 @@ import LynxKiteEdge from "./LynxKiteEdge.tsx";
42
  import { LynxKiteState } from "./LynxKiteState";
43
  import NodeSearch, { type OpsOp, type Catalog, type Catalogs } from "./NodeSearch.tsx";
44
  import NodeWithGraphCreationView from "./nodes/GraphCreationNode.tsx";
 
45
  import NodeWithComment from "./nodes/NodeWithComment.tsx";
46
  import NodeWithImage from "./nodes/NodeWithImage.tsx";
47
  import NodeWithMolecule from "./nodes/NodeWithMolecule.tsx";
@@ -83,7 +84,7 @@ function LynxKiteFlow() {
83
  if (!state.workspace.nodes) return;
84
  if (!state.workspace.edges) return;
85
  for (const n of state.workspace.nodes) {
86
- if (n.type !== "group" && n.dragHandle !== ".drag-handle") {
87
  n.dragHandle = ".drag-handle";
88
  }
89
  }
@@ -190,6 +191,7 @@ function LynxKiteFlow() {
190
  graph_creation_view: NodeWithGraphCreationView,
191
  molecule: NodeWithMolecule,
192
  comment: NodeWithComment,
 
193
  }),
194
  [],
195
  );
@@ -364,7 +366,7 @@ function LynxKiteFlow() {
364
  const selectedNodes = nodes.filter((n) => n.selected);
365
  const groupNode = {
366
  id: findFreeId("Group"),
367
- type: "group",
368
  position: { x: 0, y: 0 },
369
  width: 0,
370
  height: 0,
@@ -418,7 +420,7 @@ function LynxKiteFlow() {
418
  }
419
  function ungroupSelection() {
420
  const groups = Object.fromEntries(
421
- nodes.filter((n) => n.selected && n.type === "group").map((n) => [n.id, n]),
422
  );
423
  setNodes(
424
  nodes
@@ -451,7 +453,7 @@ function LynxKiteFlow() {
451
  });
452
  }
453
  const areMultipleNodesSelected = nodes.filter((n) => n.selected).length > 1;
454
- const isAnyGroupSelected = nodes.some((n) => n.selected && n.type === "group");
455
  return (
456
  <div className="workspace">
457
  <div className="top-bar bg-neutral">
 
42
  import { LynxKiteState } from "./LynxKiteState";
43
  import NodeSearch, { type OpsOp, type Catalog, type Catalogs } from "./NodeSearch.tsx";
44
  import NodeWithGraphCreationView from "./nodes/GraphCreationNode.tsx";
45
+ import Group from "./nodes/Group.tsx";
46
  import NodeWithComment from "./nodes/NodeWithComment.tsx";
47
  import NodeWithImage from "./nodes/NodeWithImage.tsx";
48
  import NodeWithMolecule from "./nodes/NodeWithMolecule.tsx";
 
84
  if (!state.workspace.nodes) return;
85
  if (!state.workspace.edges) return;
86
  for (const n of state.workspace.nodes) {
87
+ if (n.type !== "node_group" && n.dragHandle !== ".drag-handle") {
88
  n.dragHandle = ".drag-handle";
89
  }
90
  }
 
191
  graph_creation_view: NodeWithGraphCreationView,
192
  molecule: NodeWithMolecule,
193
  comment: NodeWithComment,
194
+ node_group: Group,
195
  }),
196
  [],
197
  );
 
366
  const selectedNodes = nodes.filter((n) => n.selected);
367
  const groupNode = {
368
  id: findFreeId("Group"),
369
+ type: "node_group",
370
  position: { x: 0, y: 0 },
371
  width: 0,
372
  height: 0,
 
420
  }
421
  function ungroupSelection() {
422
  const groups = Object.fromEntries(
423
+ nodes.filter((n) => n.selected && n.type === "node_group").map((n) => [n.id, n]),
424
  );
425
  setNodes(
426
  nodes
 
453
  });
454
  }
455
  const areMultipleNodesSelected = nodes.filter((n) => n.selected).length > 1;
456
+ const isAnyGroupSelected = nodes.some((n) => n.selected && n.type === "node_group");
457
  return (
458
  <div className="workspace">
459
  <div className="top-bar bg-neutral">
lynxkite-app/web/src/workspace/nodes/Group.tsx ADDED
@@ -0,0 +1,63 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { cursorTo } from "node:readline";
2
+ import { useReactFlow } from "@xyflow/react";
3
+ import { useState } from "react";
4
+ // @ts-ignore
5
+ import Palette from "~icons/tabler/palette-filled.jsx";
6
+ // @ts-ignore
7
+ import Square from "~icons/tabler/square-filled.jsx";
8
+ import Tooltip from "../../Tooltip.tsx";
9
+ import { COLORS } from "../../common.ts";
10
+
11
+ export default function Group(props: any) {
12
+ const reactFlow = useReactFlow();
13
+ const [displayingColorPicker, setDisplayingColorPicker] = useState(false);
14
+ function setColor(newValue: string) {
15
+ reactFlow.updateNodeData(props.id, (prevData: any) => ({
16
+ ...prevData,
17
+ params: { color: newValue },
18
+ }));
19
+ setDisplayingColorPicker(false);
20
+ }
21
+ function toggleColorPicker(e: React.MouseEvent<HTMLButtonElement, MouseEvent>) {
22
+ console.log("toggleColorPicker", displayingColorPicker);
23
+ e.stopPropagation();
24
+ setDisplayingColorPicker(!displayingColorPicker);
25
+ }
26
+ const currentColor = props.data?.params?.color || "gray";
27
+ return (
28
+ <div
29
+ className="node-group"
30
+ style={{
31
+ width: props.width,
32
+ height: props.height,
33
+ backgroundColor: COLORS[currentColor],
34
+ opacity: 0.9,
35
+ }}
36
+ >
37
+ <button className="node-group-color-picker-icon" onClick={toggleColorPicker}>
38
+ <Tooltip doc="Change color">
39
+ <Palette />
40
+ </Tooltip>
41
+ </button>
42
+ {displayingColorPicker && <ColorPicker currentColor={currentColor} onPick={setColor} />}
43
+ </div>
44
+ );
45
+ }
46
+
47
+ function ColorPicker(props: { currentColor: string; onPick: (color: string) => void }) {
48
+ const colors = Object.keys(COLORS).filter((color) => color !== props.currentColor);
49
+ return (
50
+ <div className="color-picker">
51
+ {colors.map((color) => (
52
+ <button
53
+ key={color}
54
+ className="color-picker-button"
55
+ style={{ color: COLORS[color] }}
56
+ onClick={() => props.onPick(color)}
57
+ >
58
+ <Square />
59
+ </button>
60
+ ))}
61
+ </div>
62
+ );
63
+ }
lynxkite-app/web/src/workspace/nodes/LynxKiteNode.tsx CHANGED
@@ -12,6 +12,7 @@ import Help from "~icons/tabler/question-mark.jsx";
12
  // @ts-ignore
13
  import Skull from "~icons/tabler/skull.jsx";
14
  import Tooltip from "../../Tooltip";
 
15
 
16
  interface LynxKiteNodeProps {
17
  id: string;
@@ -20,6 +21,7 @@ interface LynxKiteNodeProps {
20
  nodeStyle: any;
21
  data: any;
22
  children: any;
 
23
  }
24
 
25
  function getHandles(inputs: any[], outputs: any[]) {
@@ -53,15 +55,6 @@ function getHandles(inputs: any[], outputs: any[]) {
53
  return handles;
54
  }
55
 
56
- const OP_COLORS: { [key: string]: string } = {
57
- gray: "oklch(95% 0 0)",
58
- pink: "oklch(75% 0.2 0)",
59
- orange: "oklch(75% 0.2 55)",
60
- green: "oklch(75% 0.2 150)",
61
- blue: "oklch(75% 0.2 230)",
62
- purple: "oklch(75% 0.2 290)",
63
- };
64
-
65
  function LynxKiteNodeComponent(props: LynxKiteNodeProps) {
66
  const reactFlow = useReactFlow();
67
  const data = props.data;
@@ -78,7 +71,7 @@ function LynxKiteNodeComponent(props: LynxKiteNodeProps) {
78
  };
79
  const titleStyle: { backgroundColor?: string } = {};
80
  if (data.meta?.value?.color) {
81
- titleStyle.backgroundColor = OP_COLORS[data.meta.value.color] || data.meta.value.color;
82
  }
83
  return (
84
  <div
 
12
  // @ts-ignore
13
  import Skull from "~icons/tabler/skull.jsx";
14
  import Tooltip from "../../Tooltip";
15
+ import { COLORS } from "../../common.ts";
16
 
17
  interface LynxKiteNodeProps {
18
  id: string;
 
21
  nodeStyle: any;
22
  data: any;
23
  children: any;
24
+ parentId?: string;
25
  }
26
 
27
  function getHandles(inputs: any[], outputs: any[]) {
 
55
  return handles;
56
  }
57
 
 
 
 
 
 
 
 
 
 
58
  function LynxKiteNodeComponent(props: LynxKiteNodeProps) {
59
  const reactFlow = useReactFlow();
60
  const data = props.data;
 
71
  };
72
  const titleStyle: { backgroundColor?: string } = {};
73
  if (data.meta?.value?.color) {
74
+ titleStyle.backgroundColor = COLORS[data.meta.value.color] || data.meta.value.color;
75
  }
76
  return (
77
  <div