// Right-side inspector for either the selected node or edge function Inspector({ selection, setSelection, nodes, edges, groups, writings, updateNode, updateEdge, updateWriting, deleteNode, deleteEdge, deleteWriting, addNode, addWriting, clearAll, onLoadDemo, createGroupFromNode, updateGroup, deleteGroup, composingGroupId, setComposingGroupId, }){ groups = groups || []; writings = writings || []; if (!selection){ return (

はじめかた

+ノード追加で人物を置こう
・アイコン右上のをドラッグして相手にドロップ → 関係線ができるよ
・線やノードをクリックで編集
何も選択されていないよ。
キャンバスのノードや線をクリックしてね。
); } if (selection.type === 'node'){ const node = nodes.find(n=>n.id===selection.id); if (!node) return null; const nodeGroups = groups.filter(g => (g.nodeIds || []).includes(node.id)); return (
); } if (selection.type === 'edge'){ const edge = edges.find(e=>e.id===selection.id); if (!edge) return null; return (
); } if (selection.type === 'writing'){ const writing = writings.find(w => w.id === selection.id); if (!writing) return null; return (
); } if (selection.type === 'group'){ const group = groups.find(g=>g.id===selection.id); if (!group) return null; return (
); } return null; } function NodeInspector({node, updateNode, deleteNode, nodeGroups, createGroupFromNode, updateGroup, deleteGroup, setComposingGroupId, setSelection}){ nodeGroups = nodeGroups || []; const fileRef = React.useRef(null); const onFile = async (e) => { const f = e.target.files?.[0]; if (!f) return; try { const dataUrl = await compressImage(f); updateNode(node.id, { image: dataUrl }); } catch(err) { console.error('Image compression failed', err); } e.target.value = ''; }; return ( <>

ノード編集

updateNode(node.id,{name:e.target.value})} placeholder="キャラの名前"/>
updateNode(node.id,{subtitle:e.target.value})} placeholder="主人公 / 高1 / etc."/>

アイコン

{node.image && }
{EMOJI_PRESETS.map(em=>( ))}
{NODE_COLORS.map(c=>(
{[['circle','まる'],['square','四角'],['bubble','吹き出し']].map(([v,l])=>( ))}
{/* Name/subtitle position relative to the avatar. Default = bottom. Useful when nodes are packed close together and labels collide. */}
{[['top','上'], ['left','左'], ['bottom','下'], ['right','右']].map(([v,l])=>( ))}
{/* Group membership — multi-membership is allowed */} {nodeGroups.length > 0 ? (
🫧 所属グループ ({nodeGroups.length})
{nodeGroups.map(ng => (
{ng.name || '(無名)'}
))}
) : ( )} ); } function GroupInspector({group, nodes, updateGroup, deleteGroup, setComposingGroupId, setSelection}){ const members = (group.nodeIds || []) .map(nid => nodes.find(n => n.id === nid)) .filter(Boolean); const presets = ['#FFB3C7','#FFD3A8','#FFE8A3','#C7EFC0','#B5D9F2','#C9BEF5','#E8BCE8']; return ( <>

🫧 グループ編集

updateGroup(group.id, {name:e.target.value})} placeholder="チーム名 / クラス名 / etc."/>

{presets.map(c=>(

メンバー ({members.length})

{members.map(n => (
{n.emoji || (n.name||'?').charAt(0).toUpperCase()} {n.name || '(無名)'}
))}
); } function EdgeInspector({edge, nodes, updateEdge, deleteEdge}){ const from = nodes.find(n=>n.id===edge.from); const to = nodes.find(n=>n.id===edge.to); const rel = RELATIONS.find(r=>r.id===edge.relation) || RELATIONS[0]; return ( <>

関係を編集

from {from?.name||'?'} to {to?.name||'?'} {' '}
{RELATIONS.map(r=>( ))}
updateEdge(edge.id,{label:e.target.value})}/>
{STAMPS.map(s=>( ))}

矢印のスタイル

{ARROW_STYLES.map(s=>( ))}
{(() => { const presets = ['#F56A92','#F77F6E','#FFB86B','#7DD8B4','#8CCBF0','#B7A0EE','#7a6a7a','#3a2c3a']; return ( <> {presets.map(c=>(
{edge.customOffset !== undefined && edge.customOffset !== 0 && ( )}
💡 別の矢印を Shift+クリック で重なり順を入れ替え
); } function WritingInspector({writing, nodes, edges, updateWriting, deleteWriting}){ const presets = ['#F56A92','#F77F6E','#FFB86B','#FFD97A','#7DD8B4','#8CCBF0','#B7A0EE','#3a2c3a']; const fontSize = writing.fontSize || 28; const rotation = writing.rotation || 0; const links = writing.links || []; // Resolve a link to a human-readable label for the link list. const linkLabel = (link) => { if (link.type === 'node') { const n = nodes.find(x => x.id === link.id); return n ? `ノード「${n.name || '?'}」` : '(削除済みのノード)'; } if (link.type === 'edge') { const ed = edges.find(x => x.id === link.id); return ed ? `矢印「${ed.label || ed.relation || ''}」` : '(削除済みの矢印)'; } return '?'; }; const removeLinkAt = (idx) => { const next = links.filter((_, i) => i !== idx); updateWriting(writing.id, { links: next }); }; const resetCurveAt = (idx) => { const next = links.map((l, i) => i === idx ? { ...l, lineCurve: 0 } : l); updateWriting(writing.id, { links: next }); }; const detachAll = () => updateWriting(writing.id, { links: [] }); return ( <>

📝 書き込み編集