Skip to content

Commit ad9592d

Browse files
committed
transition cards, draggable nodecards, morph changes using radio buttons
1 parent b14f682 commit ad9592d

8 files changed

Lines changed: 610 additions & 49 deletions

File tree

nodebook-base/frontend/src/App.tsx

Lines changed: 67 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import { MediaManager } from './MediaManager';
2525
import { GraphScore } from './GraphScore';
2626
import { CompactScoreDisplay } from './CompactScoreDisplay';
2727
import { SlideShow } from './SlideShow';
28+
import { DraggableModal } from './DraggableModal';
2829
import { calculateGraphScore } from './utils/graphScoring';
2930
import { ensureDescriptionBlocks, extractDescriptionsForAnalysis, debugDescriptions } from './utils/cnlProcessor';
3031
import { analyzeMultipleTexts, type NLPAnalysisResult, type NLPAnalysisError } from './services/nlpAnalysisService';
@@ -128,6 +129,7 @@ function App({ onLogout, onGoToDashboard, user }: AppProps) {
128129
const [attributeTypes, setAttributeTypes] = useState<AttributeType[]>([]);
129130
const [nodeTypes, setNodeTypes] = useState<any[]>([]);
130131
const [selectedNodeId, setSelectedNodeId] = useState<string | null>(null);
132+
const [isNodeCardOpen, setIsNodeCardOpen] = useState<boolean>(false);
131133
const [graphMode, setGraphMode] = useState<'markdown' | 'mindmap' | 'richgraph' | 'strictgraph'>('richgraph');
132134
// Single CNL text for the current graph
133135
const [cnlText, setCnlText] = useState<string>('');
@@ -178,6 +180,45 @@ function App({ onLogout, onGoToDashboard, user }: AppProps) {
178180
alert(`Failed to change morph: ${error.message}`);
179181
}
180182
};
183+
184+
// Transition simulation handler
185+
const handleTransitionSimulate = async (transitionId: string) => {
186+
try {
187+
console.log(`[App] Simulating transition ${transitionId}`);
188+
189+
// For now, we'll just show an alert. In the future, this could:
190+
// 1. Call a backend API to simulate the transition
191+
// 2. Update node states based on the transition
192+
// 3. Show animation or visual feedback
193+
194+
const transitionNode = nodes.find(n => n.id === transitionId);
195+
if (transitionNode) {
196+
alert(`Simulating transition: ${transitionNode.name}\n\nThis would transform the prior states into post states according to the transition rules.`);
197+
}
198+
199+
} catch (error) {
200+
console.error('[App] Error simulating transition:', error);
201+
alert('Failed to simulate transition. See console for details.');
202+
}
203+
};
204+
205+
// Enhanced node selection handler
206+
const handleNodeSelect = (nodeId: string | null) => {
207+
if (nodeId) {
208+
// Clicking on a node: select it and open/keep modal open
209+
setSelectedNodeId(nodeId);
210+
setIsNodeCardOpen(true);
211+
} else {
212+
// Clicking on empty space: close modal but keep selected node for reference
213+
setIsNodeCardOpen(false);
214+
}
215+
};
216+
217+
// Handler for closing the modal
218+
const handleCloseNodeCard = () => {
219+
setIsNodeCardOpen(false);
220+
setSelectedNodeId(null);
221+
};
181222
const [isWordNetLoading, setIsWordNetLoading] = useState(false);
182223
const [wordNetError, setWordNetError] = useState<string | null>(null);
183224
const [strictMode, setStrictMode] = useState<boolean>(false);
@@ -741,24 +782,30 @@ function App({ onLogout, onGoToDashboard, user }: AppProps) {
741782
/>
742783
) : (
743784
<>
744-
<Visualization nodes={nodes} relations={relations} attributes={attributes} onNodeSelect={setSelectedNodeId} onMorphChange={handleMorphChange} graphMode={graphMode === 'strictgraph' ? 'richgraph' : graphMode} />
745-
{selectedNode && (
746-
<div className={styles.selectedNodeCard}>
785+
<Visualization nodes={nodes} relations={relations} attributes={attributes} onNodeSelect={handleNodeSelect} onMorphChange={handleMorphChange} graphMode={graphMode === 'strictgraph' ? 'richgraph' : graphMode} />
786+
<DraggableModal
787+
isOpen={isNodeCardOpen && !!selectedNode}
788+
onClose={handleCloseNodeCard}
789+
title={selectedNode ? `${selectedNode.name} ${selectedNode.role === 'Transition' ? '(Transition)' : ''}` : ''}
790+
initialPosition={{ x: 100, y: 100 }}
791+
>
792+
{selectedNode && (
747793
<NodeCard
748794
node={selectedNode}
749795
allNodes={nodes}
750796
allRelations={relations}
751-
attributes={attributeTypes}
797+
attributes={attributes}
752798
isActive={false}
753799
onSelectNode={(nodeId) => console.log('Node selected:', nodeId)}
754800
onImportContext={(nodeId) => console.log('Import context:', nodeId)}
755801
nodeRegistry={{}}
756802
isPublic={false}
757803
graphId={activeGraphId || undefined}
758804
onMorphChange={handleMorphChange}
805+
onTransitionSimulate={handleTransitionSimulate}
759806
/>
760-
</div>
761-
)}
807+
)}
808+
</DraggableModal>
762809
</>
763810
)}
764811
</div>
@@ -824,22 +871,30 @@ function App({ onLogout, onGoToDashboard, user }: AppProps) {
824871
)}
825872
{viewMode === 'visualization' && (
826873
<div className={styles.visualizationWrapper}>
827-
<Visualization nodes={nodes} relations={relations} attributes={attributes} onNodeSelect={setSelectedNodeId} onMorphChange={handleMorphChange} graphMode={graphMode === 'strictgraph' ? 'richgraph' : (graphMode === 'markdown' ? 'richgraph' : graphMode)} />
828-
{selectedNode && (
829-
<div className={styles.selectedNodeCard}>
874+
<Visualization nodes={nodes} relations={relations} attributes={attributes} onNodeSelect={handleNodeSelect} onMorphChange={handleMorphChange} graphMode={graphMode === 'strictgraph' ? 'richgraph' : (graphMode === 'markdown' ? 'richgraph' : graphMode)} />
875+
<DraggableModal
876+
isOpen={isNodeCardOpen && !!selectedNode}
877+
onClose={handleCloseNodeCard}
878+
title={selectedNode ? `${selectedNode.name} ${selectedNode.role === 'Transition' ? '(Transition)' : ''}` : ''}
879+
initialPosition={{ x: 150, y: 150 }}
880+
>
881+
{selectedNode && (
830882
<NodeCard
831883
node={selectedNode}
832884
allNodes={nodes}
833885
allRelations={relations}
834-
attributes={attributeTypes}
886+
attributes={attributes}
835887
isActive={false}
836888
onSelectNode={(nodeId) => console.log('Node selected:', nodeId)}
837889
onImportContext={(nodeId) => console.log('Import context:', nodeId)}
838890
nodeRegistry={{}}
839891
isPublic={false}
892+
graphId={activeGraphId || undefined}
893+
onMorphChange={handleMorphChange}
894+
onTransitionSimulate={handleTransitionSimulate}
840895
/>
841-
</div>
842-
)}
896+
)}
897+
</DraggableModal>
843898
</div>
844899
)}
845900
{viewMode === 'slideshow' && (
Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
/* Draggable Modal Styles */
2+
.draggable-modal-overlay {
3+
position: fixed;
4+
top: 0;
5+
left: 0;
6+
right: 0;
7+
bottom: 0;
8+
background-color: rgba(0, 0, 0, 0.3);
9+
z-index: 1000;
10+
display: flex;
11+
align-items: flex-start;
12+
justify-content: flex-start;
13+
}
14+
15+
.draggable-modal {
16+
position: absolute;
17+
background: white;
18+
border-radius: 8px;
19+
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.2);
20+
min-width: 400px;
21+
max-width: 600px;
22+
max-height: 80vh;
23+
overflow: hidden;
24+
transition: box-shadow 0.2s ease;
25+
}
26+
27+
.draggable-modal.dragging {
28+
box-shadow: 0 15px 35px rgba(0, 0, 0, 0.3);
29+
cursor: grabbing;
30+
}
31+
32+
.modal-header {
33+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
34+
color: white;
35+
padding: 12px 16px;
36+
cursor: grab;
37+
user-select: none;
38+
display: flex;
39+
justify-content: space-between;
40+
align-items: center;
41+
border-radius: 8px 8px 0 0;
42+
}
43+
44+
.modal-header:active {
45+
cursor: grabbing;
46+
}
47+
48+
.modal-title {
49+
display: flex;
50+
align-items: center;
51+
gap: 8px;
52+
flex: 1;
53+
}
54+
55+
.modal-title-text {
56+
font-weight: 600;
57+
font-size: 1rem;
58+
}
59+
60+
.modal-drag-handle {
61+
font-size: 12px;
62+
opacity: 0.7;
63+
letter-spacing: 2px;
64+
line-height: 1;
65+
}
66+
67+
.modal-close-btn {
68+
background: none;
69+
border: none;
70+
color: white;
71+
font-size: 24px;
72+
cursor: pointer;
73+
padding: 0;
74+
width: 24px;
75+
height: 24px;
76+
display: flex;
77+
align-items: center;
78+
justify-content: center;
79+
border-radius: 4px;
80+
transition: background-color 0.2s ease;
81+
}
82+
83+
.modal-close-btn:hover {
84+
background-color: rgba(255, 255, 255, 0.2);
85+
}
86+
87+
.modal-content {
88+
padding: 0;
89+
max-height: calc(80vh - 60px);
90+
overflow-y: auto;
91+
}
92+
93+
/* Responsive design */
94+
@media (max-width: 768px) {
95+
.draggable-modal {
96+
min-width: 90vw;
97+
max-width: 95vw;
98+
max-height: 90vh;
99+
}
100+
101+
.modal-header {
102+
padding: 10px 12px;
103+
}
104+
105+
.modal-title-text {
106+
font-size: 0.9rem;
107+
}
108+
109+
.modal-content {
110+
max-height: calc(90vh - 50px);
111+
}
112+
}
113+
114+
/* Smooth transitions */
115+
.draggable-modal-overlay {
116+
animation: fadeIn 0.2s ease-out;
117+
}
118+
119+
@keyframes fadeIn {
120+
from {
121+
opacity: 0;
122+
}
123+
to {
124+
opacity: 1;
125+
}
126+
}
127+
128+
.draggable-modal {
129+
animation: slideIn 0.3s ease-out;
130+
}
131+
132+
@keyframes slideIn {
133+
from {
134+
opacity: 0;
135+
transform: scale(0.9) translateY(-20px);
136+
}
137+
to {
138+
opacity: 1;
139+
transform: scale(1) translateY(0);
140+
}
141+
}

0 commit comments

Comments
 (0)