Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 38 additions & 38 deletions app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { WelcomeModal } from '@/components/WelcomeModal';
import { AuthModal } from '@/components/auth/AuthModal';
import MobileWarning from '@/components/MobileWarning';
import { useAuth } from '@/hooks/useAuth';
import { MIN_ZOOM, MAX_ZOOM, ZOOM_STEP, type GraphData, type DocumentData, type GraphNode } from '@/lib/types';
import { MIN_ZOOM, MAX_ZOOM, ZOOM_STEP, NodeType, EdgeType, type GraphData, type DocumentData, type GraphNode } from '@/lib/types';
import { startNodeIndex, AUTHORS_ESTIMATES, graphData as defaultGraphData } from '@/lib/graphData';
import { calculateProbabilities } from '@/lib/probability';
import { loadFromLocalStorage, saveToLocalStorage, createDefaultDocumentData, clearLocalStorage, createEmptyDocumentData } from '@/lib/documentState';
Expand Down Expand Up @@ -157,7 +157,7 @@ function HomeContent() {
if (node.probability === undefined) {
return {
...node,
probability: node.type === 'n' ? 50 : null
probability: node.type === NodeType.QUESTION ? 50 : null
};
}
return node;
Expand Down Expand Up @@ -191,7 +191,7 @@ function HomeContent() {
if (node.probability === undefined) {
return {
...node,
probability: node.type === 'n' ? 50 : null
probability: node.type === NodeType.QUESTION ? 50 : null
};
}
return node;
Expand Down Expand Up @@ -250,7 +250,7 @@ function HomeContent() {

// Find max probability among outcome nodes (good, ambivalent, existential)
const outcomeNodes = result.nodes.filter(
n => n.type === 'g' || n.type === 'a' || n.type === 'e'
n => n.type === NodeType.GOOD || n.type === NodeType.AMBIVALENT || n.type === NodeType.EXISTENTIAL
);
const maxOutcomeProbability = Math.max(
...outcomeNodes.map(n => n.p),
Expand Down Expand Up @@ -510,16 +510,16 @@ function HomeContent() {

if (updatedConnections.length === 0) {
// No connections left: convert to AMBIVALENT outcome
updatedType = 'a';
updatedType = NodeType.AMBIVALENT;
updatedSliderIndex = null; // Clear sliderIndex
} else if (updatedConnections.length === 1) {
// One connection left: convert from QUESTION to INTERMEDIATE
// Also convert the remaining connection from YES/NO to E100
updatedType = 'i';
updatedType = NodeType.INTERMEDIATE;
updatedSliderIndex = null; // Clear sliderIndex
finalConnections = updatedConnections.map(conn => ({
...conn,
type: '-' as const,
type: EdgeType.ALWAYS,
label: '',
}));
}
Expand All @@ -532,7 +532,7 @@ function HomeContent() {
// Re-index remaining question nodes if we removed a question
if (sliderIndexToRemove !== null && sliderIndexToRemove !== undefined) {
updatedNodes = updatedNodes.map(n => {
if (n.type === 'n' && n.sliderIndex !== null && n.sliderIndex > sliderIndexToRemove) {
if (n.type === NodeType.QUESTION && n.sliderIndex !== null && n.sliderIndex > sliderIndexToRemove) {
return { ...n, sliderIndex: n.sliderIndex - 1 };
}
return n;
Expand Down Expand Up @@ -563,11 +563,11 @@ function HomeContent() {
const newEdgeIndex = currentConnectionCount; // Will be added at this index

// Determine if this will convert to a question node
const isIntermediateNode = targetNode.type === 'i';
const isIntermediateNode = targetNode.type === NodeType.INTERMEDIATE;
const willBecomeQuestion = isIntermediateNode && targetNode.connections.length === 1;

// Calculate the next sliderIndex BEFORE state updates (for new question nodes)
const existingQuestions = graphData.nodes.filter(n => n.type === 'n');
const existingQuestions = graphData.nodes.filter(n => n.type === NodeType.QUESTION);
const maxSliderIndex = existingQuestions.reduce(
(max, n) => n.sliderIndex !== null && n.sliderIndex > max ? n.sliderIndex : max,
-1
Expand Down Expand Up @@ -613,32 +613,32 @@ function HomeContent() {
}
}

const isOutcomeNode = node.type === 'g' || node.type === 'a' || node.type === 'e';
const isIntermediateNode = node.type === 'i';
const isOutcomeNode = node.type === NodeType.GOOD || node.type === NodeType.AMBIVALENT || node.type === NodeType.EXISTENTIAL;
const isIntermediateNode = node.type === NodeType.INTERMEDIATE;

if (isOutcomeNode && node.connections.length === 0) {
// Case: OUTCOME node with 0 connections -> add 1 E100 connection -> convert to INTERMEDIATE
const newConnection = {
type: '-' as const,
type: EdgeType.ALWAYS,
targetX,
targetY,
label: '',
};

return { ...node, connections: [newConnection], type: 'i' as const };
return { ...node, connections: [newConnection], type: NodeType.INTERMEDIATE };
} else if (isIntermediateNode && node.connections.length === 1) {
// Case: INTERMEDIATE node with 1 connection -> add YES/NO connections -> convert to QUESTION
// Convert existing connection from E100 to YES
const updatedExistingConnections = node.connections.map(conn => {
if (conn.type === '-') {
return { ...conn, type: 'y' as const, label: 'Yes' };
if (conn.type === EdgeType.ALWAYS) {
return { ...conn, type: EdgeType.YES, label: 'Yes' };
}
return conn;
});

// Add new connection with NO type
const newConnection = {
type: 'n' as const,
type: EdgeType.NO,
targetX,
targetY,
label: 'No',
Expand All @@ -648,7 +648,7 @@ function HomeContent() {

// Convert node from INTERMEDIATE to QUESTION
// CRITICAL: Assign sliderIndex and initialize probability
return { ...node, connections: updatedConnections, type: 'n' as const, sliderIndex: newSliderIndex, probability: 50 };
return { ...node, connections: updatedConnections, type: NodeType.QUESTION, sliderIndex: newSliderIndex, probability: 50 };
}
}
return node;
Expand Down Expand Up @@ -776,7 +776,7 @@ function HomeContent() {

setGraphData(prev => {
const updatedNodes = prev.nodes.map(node => {
if (node.type === 'n' && node.sliderIndex !== null) {
if (node.type === NodeType.QUESTION && node.sliderIndex !== null) {
return { ...node, probability: 50 };
}
return node;
Expand All @@ -798,7 +798,7 @@ function HomeContent() {
// Only update probability for question nodes that exist in the default data
// AND have a valid probability value (not null or undefined)
// Custom nodes and nodes without default probabilities are left unchanged
if (node.type === 'n' && node.sliderIndex !== null && defaultNode && defaultNode.probability != null) {
if (node.type === NodeType.QUESTION && node.sliderIndex !== null && defaultNode && defaultNode.probability != null) {
// Preserve all node fields (including custom title), only update probability
return { ...node, probability: defaultNode.probability };
}
Expand Down Expand Up @@ -1004,7 +1004,7 @@ function HomeContent() {
if (node.probability === undefined) {
return {
...node,
probability: node.type === 'n' ? 50 : null
probability: node.type === NodeType.QUESTION ? 50 : null
};
}
return node;
Expand Down Expand Up @@ -1099,7 +1099,7 @@ function HomeContent() {
// Create a new ambivalent outcome node at the clicked position
const newNode = {
id: newNodeId,
type: 'a' as const, // Ambivalent outcome node type
type: NodeType.AMBIVALENT, // Ambivalent outcome node type
title: '',
connections: [], // Outcome nodes have no outgoing connections
position: { x, y },
Expand Down Expand Up @@ -1136,7 +1136,7 @@ function HomeContent() {
// Create a new ambivalent outcome node at the specified position
const newNode = {
id: newNodeId,
type: 'a' as const, // Ambivalent outcome node type
type: NodeType.AMBIVALENT, // Ambivalent outcome node type
title: '',
connections: [], // Outcome nodes have no outgoing connections
position: { x: position.x, y: position.y },
Expand Down Expand Up @@ -1257,7 +1257,7 @@ function HomeContent() {
if (!node) return;

// Prevent deleting the start node
if (node.type === 's') {
if (node.type === NodeType.START) {
alert('Cannot delete the start node');
return;
}
Expand All @@ -1272,12 +1272,12 @@ function HomeContent() {
}, [handleDeleteEdge]);

// Change node type handler
const handleChangeNodeType = useCallback((nodeId: string, newType: 'n' | 'i' | 'g' | 'a' | 'e') => {
const handleChangeNodeType = useCallback((nodeId: string, newType: NodeType) => {
const node = graphData.nodes.find(n => n.id === nodeId);
if (!node) return;

// Don't allow changing start node type
if (node.type === 's') {
if (node.type === NodeType.START) {
alert('Cannot change the type of the start node');
return;
}
Expand All @@ -1291,9 +1291,9 @@ function HomeContent() {
if (n.id !== nodeId) return n;

// Changing TO question node
if (newType === 'n') {
if (newType === NodeType.QUESTION) {
// Find the highest sliderIndex among existing questions
const questionNodes = prev.nodes.filter(node => node.type === 'n');
const questionNodes = prev.nodes.filter(node => node.type === NodeType.QUESTION);
const maxSliderIndex = questionNodes.reduce((max, node) =>
node.sliderIndex !== null && node.sliderIndex > max ? node.sliderIndex : max, -1);
const newSliderIndex = maxSliderIndex + 1;
Expand All @@ -1303,17 +1303,17 @@ function HomeContent() {
if (connections.length === 0) {
// Create 2 new free-floating connections
connections = [
{ type: 'y' as const, targetX: n.position.x + 75, targetY: n.position.y - 50, label: 'Yes' },
{ type: 'n' as const, targetX: n.position.x + 75, targetY: n.position.y + 50, label: 'No' },
{ type: EdgeType.YES, targetX: n.position.x + 75, targetY: n.position.y - 50, label: 'Yes' },
{ type: EdgeType.NO, targetX: n.position.x + 75, targetY: n.position.y + 50, label: 'No' },
];
} else if (connections.length === 1) {
// Keep existing as YES, add NO
connections[0] = { ...connections[0], type: 'y' as const };
connections.push({ type: 'n' as const, targetX: n.position.x + 75, targetY: n.position.y + 50, label: 'No' });
connections[0] = { ...connections[0], type: EdgeType.YES };
connections.push({ type: EdgeType.NO, targetX: n.position.x + 75, targetY: n.position.y + 50, label: 'No' });
} else {
// Has 2+ connections: convert first to YES, second to NO, keep rest as-is
connections[0] = { ...connections[0], type: 'y' as const };
connections[1] = { ...connections[1], type: 'n' as const };
connections[0] = { ...connections[0], type: EdgeType.YES };
connections[1] = { ...connections[1], type: EdgeType.NO };
}

return {
Expand All @@ -1325,11 +1325,11 @@ function HomeContent() {
}

// Changing FROM question node to something else
if (oldType === 'n') {
if (oldType === NodeType.QUESTION) {
// Convert YES/NO connections to ALWAYS
const connections = n.connections.map(conn => ({
...conn,
type: '-' as const,
type: EdgeType.ALWAYS,
}));

return {
Expand All @@ -1348,12 +1348,12 @@ function HomeContent() {
});

// If changing FROM question, need to re-index remaining questions and update sliderValues
if (oldType === 'n') {
if (oldType === NodeType.QUESTION) {
const oldSliderIndex = node.sliderIndex;
if (oldSliderIndex !== null) {
// Re-index all questions that had higher indices
const finalNodes = updatedNodes.map(n => {
if (n.type === 'n' && n.sliderIndex !== null && n.sliderIndex > oldSliderIndex) {
if (n.type === NodeType.QUESTION && n.sliderIndex !== null && n.sliderIndex > oldSliderIndex) {
return { ...n, sliderIndex: n.sliderIndex - 1 };
}
return n;
Expand Down
3 changes: 2 additions & 1 deletion components/AddArrowButtons.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { ArrowUp, ArrowDown, ArrowLeft, ArrowRight } from 'lucide-react';
import { NodeType as NT } from '@/lib/types';
import Tooltip from './Tooltip';

interface AddArrowButtonsProps {
Expand All @@ -19,7 +20,7 @@ export default function AddArrowButtons({
const offset = 15; // Distance from node edge

// Determine if this is an outcome node (has colored bubbles on the left)
const isOutcomeNode = nodeType === 'g' || nodeType === 'a' || nodeType === 'e';
const isOutcomeNode = nodeType === NT.GOOD || nodeType === NT.AMBIVALENT || nodeType === NT.EXISTENTIAL;

const allButtons = [
{
Expand Down
13 changes: 7 additions & 6 deletions components/ConnectionEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import { useState } from 'react';
import type { GraphNode, NodeConnection, EdgeType } from '@/lib/types';
import { EdgeType as ET } from '@/lib/types';

interface ConnectionEditorProps {
selectedNode: GraphNode | null;
Expand Down Expand Up @@ -46,11 +47,11 @@ export default function ConnectionEditor({

const getEdgeTypeLabel = (type: EdgeType): string => {
switch (type) {
case 'y':
case ET.YES:
return 'YES';
case 'n':
case ET.NO:
return 'NO';
case '-':
case ET.ALWAYS:
return 'ALWAYS';
default:
return type;
Expand All @@ -59,11 +60,11 @@ export default function ConnectionEditor({

const getEdgeTypeColor = (type: EdgeType): string => {
switch (type) {
case 'y':
case ET.YES:
return 'text-green-400';
case 'n':
case ET.NO:
return 'text-red-400';
case '-':
case ET.ALWAYS:
return 'text-blue-400';
default:
return 'text-gray-400';
Expand Down
2 changes: 1 addition & 1 deletion components/Edge.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -277,7 +277,7 @@ export default function Edge({
// Calculate label text
let labelText = '';
let labelDescription = '';
if (yn !== ET.E100) {
if (yn !== ET.ALWAYS) {
const value = yn === ET.YES ? sliderValue : (sliderValue !== null ? 100 - sliderValue : null);
if (value !== null) {
// Get descriptive label from edge data
Expand Down
14 changes: 7 additions & 7 deletions components/Flowchart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@ import {
ZOOM_SENSITIVITY,
CANVAS_PADDING,
NodeType as NT,
EdgeType as ET,
type GraphData,
type NodeType as NodeTypeValue,
} from "@/lib/types";
import NodeComponent from "./Node";
import EdgeComponent from "./Edge";
Expand Down Expand Up @@ -62,10 +64,7 @@ interface FlowchartProps {
onAddNode?: (x: number, y: number) => void;
onNodeSelect?: (nodeId: string | null) => void;
onDeleteNode?: (nodeId: string) => void;
onChangeNodeType?: (
nodeId: string,
newType: "n" | "i" | "g" | "a" | "e"
) => void;
onChangeNodeType?: (nodeId: string, newType: NodeTypeValue) => void;
onEdgeClick?: (edgeIndex: number) => void;
onEdgeReconnect?: (
edgeIndex: number,
Expand Down Expand Up @@ -475,7 +474,6 @@ export default function Flowchart({
onBackgroundClick,
selectedEdgeIndex,
selectedNodeId,
editorCloseTimestampRef,
]
);

Expand Down Expand Up @@ -683,7 +681,7 @@ export default function Flowchart({
const noEdgesFromNode = edges.filter(
(e) =>
nodes[e.source].id === sourceNode.id &&
e.yn === "n" &&
e.yn === ET.NO &&
e.label === "No"
);
const newestNoEdge =
Expand Down Expand Up @@ -777,7 +775,9 @@ export default function Flowchart({
const outgoingEdges = edges.filter((e) => e.source === node.index);
const isIntermediate = outgoingEdges.length === 1;
const isOutcomeWithNoArrows =
(node.type === "g" || node.type === "a" || node.type === "e") &&
(node.type === NT.GOOD ||
node.type === NT.AMBIVALENT ||
node.type === NT.EXISTENTIAL) &&
outgoingEdges.length === 0;
const shouldShowAddArrows =
node.type !== NT.START &&
Expand Down
Loading