MetadataPanel.jsx
1 import React, { useState, useEffect } from 'react'; 2 import Select from 'react-select'; 3 import { readMetadata, writeMetadata, getAllRepoNamesAndTypes } from '../services/electronService'; 4 import { BLACK, BLUE, RED, WHITE } from '../constants/colors'; 5 import CustomNumberInput from './CustomNumberInput'; 6 import { metadataTemplate, getDefaultValue } from '../utils/metadataTemplate'; 7 8 const MetadataPanel = ({ isOpen, onClose, repoName }) => { 9 const [metadata, setMetadata] = useState({}); 10 const [isLoading, setIsLoading] = useState(true); 11 const [error, setError] = useState(null); 12 const [relatedNodesOptions, setRelatedNodesOptions] = useState([]); 13 14 useEffect(() => { 15 if (isOpen && repoName) { 16 loadMetadata(); 17 } 18 }, [isOpen, repoName]); 19 20 useEffect(() => { 21 if (metadata.type) { 22 fetchRelatedNodesOptions(); 23 } 24 }, [metadata.type]); 25 26 const loadMetadata = async () => { 27 setIsLoading(true); 28 setError(null); 29 try { 30 const data = await readMetadata(repoName); 31 // Ensure all fields have a default value 32 const defaultMetadata = { 33 ...metadataTemplate, 34 ...data 35 }; 36 setMetadata(defaultMetadata); 37 } catch (err) { 38 setError('Failed to load metadata'); 39 console.error('Error loading metadata:', err); 40 } 41 setIsLoading(false); 42 }; 43 44 const fetchRelatedNodesOptions = async () => { 45 try { 46 const repos = await getAllRepoNamesAndTypes(); 47 const currentNodeType = metadata.type; 48 const filteredRepos = repos.filter(repo => repo.type !== currentNodeType); 49 setRelatedNodesOptions(filteredRepos.map(repo => ({ value: repo.name, label: repo.name }))); 50 } catch (error) { 51 console.error('Error fetching repo names and types:', error); 52 setRelatedNodesOptions([]); 53 } 54 }; 55 56 const handleInputChange = (key, value) => { 57 setMetadata(prev => ({ ...prev, [key]: value })); 58 }; 59 60 const handleSave = async () => { 61 try { 62 await writeMetadata(repoName, metadata); 63 onClose(); 64 } catch (err) { 65 setError('Failed to save metadata: ' + err.message); 66 console.error('Error saving metadata:', err); 67 } 68 }; 69 70 const renderInput = (key, value) => { 71 if (key === 'type') { 72 return ( 73 <div style={{ display: 'flex', justifyContent: 'center' }}> 74 <label style={{ marginRight: '10px', color: BLUE }}> 75 <input 76 type="radio" 77 value="idea" 78 checked={value === 'idea'} 79 onChange={() => handleInputChange(key, 'idea')} 80 style={{ accentColor: BLUE }} 81 /> Idea 82 </label> 83 <label style={{ color: RED }}> 84 <input 85 type="radio" 86 value="person" 87 checked={value === 'person'} 88 onChange={() => handleInputChange(key, 'person')} 89 style={{ accentColor: RED }} 90 /> Person 91 </label> 92 </div> 93 ); 94 } else if (key === 'interactions') { 95 return ( 96 <CustomNumberInput 97 value={value || 0} 98 onChange={(newValue) => handleInputChange(key, newValue)} 99 /> 100 ); 101 } else if (key === 'relatedNodes') { 102 return ( 103 <Select 104 isMulti 105 options={relatedNodesOptions} 106 value={Array.isArray(value) ? value.map(v => ({ value: v, label: v })) : []} 107 onChange={(selectedOptions) => { 108 handleInputChange(key, selectedOptions ? selectedOptions.map(option => option.value) : []); 109 }} 110 styles={{ 111 control: (provided) => ({ 112 ...provided, 113 backgroundColor: BLACK, 114 borderColor: BLUE, 115 width: '450px', 116 '&:hover': { 117 borderColor: RED 118 } 119 }), 120 menu: (provided) => ({ 121 ...provided, 122 backgroundColor: BLACK, 123 }), 124 option: (provided, state) => ({ 125 ...provided, 126 backgroundColor: state.isFocused 127 ? (metadata.type === 'idea' ? BLUE : RED) 128 : BLACK, 129 color: WHITE, 130 }), 131 multiValue: (provided) => ({ 132 ...provided, 133 backgroundColor: BLACK, 134 border: `2px solid ${metadata.type === 'idea' ? RED : BLUE}`, 135 }), 136 multiValueLabel: (provided) => ({ 137 ...provided, 138 color: WHITE, 139 }), 140 multiValueRemove: (provided) => ({ 141 ...provided, 142 color: WHITE, 143 ':hover': { 144 backgroundColor: metadata.type === 'idea' ? RED : BLUE, 145 color: WHITE, 146 }, 147 }), 148 }} 149 /> 150 ); 151 } else if (key === 'friendsToNotify') { 152 return ( 153 <div style={{ maxHeight: '200px', overflowY: 'auto', border: `1px solid ${BLUE}`, padding: '5px' }}> 154 {Array.isArray(value) && value.map((friend, index) => ( 155 <div key={index} style={{ marginBottom: '10px', padding: '5px', border: `1px solid ${RED}` }}> 156 <div>Name: {friend.name}</div> 157 <div>Email: {friend.email}</div> 158 <div>Common Submodules: {friend.commonSubmodules.join(', ')}</div> 159 </div> 160 ))} 161 </div> 162 ); 163 } else if (key === 'email') { 164 return ( 165 <input 166 type="email" 167 value={value || ''} 168 onChange={(e) => handleInputChange(key, e.target.value)} 169 style={{ 170 width: '60%', 171 padding: '5px', 172 backgroundColor: BLACK, 173 color: WHITE, 174 border: `1px solid ${BLUE}`, 175 borderRadius: '4px', 176 outline: 'none', 177 }} 178 onFocus={(e) => { 179 e.target.style.border = `1px solid ${RED}`; 180 }} 181 onBlur={(e) => { 182 e.target.style.border = `1px solid ${BLUE}`; 183 }} 184 /> 185 ); 186 } else { 187 return ( 188 <input 189 type="text" 190 value={value || ''} 191 onChange={(e) => handleInputChange(key, e.target.value)} 192 style={{ 193 width: '60%', 194 padding: '5px', 195 backgroundColor: BLACK, 196 color: WHITE, 197 border: `1px solid ${BLUE}`, 198 borderRadius: '4px', 199 outline: 'none', 200 }} 201 onFocus={(e) => { 202 e.target.style.border = `1px solid ${RED}`; 203 }} 204 onBlur={(e) => { 205 e.target.style.border = `1px solid ${BLUE}`; 206 }} 207 /> 208 ); 209 } 210 }; 211 212 if (!isOpen) return null; 213 214 return ( 215 <div 216 style={{ 217 position: 'fixed', 218 top: '50%', 219 left: '50%', 220 transform: 'translate(-50%, -50%)', 221 backgroundColor: BLACK, 222 color: WHITE, 223 padding: '20px', 224 borderRadius: '8px', 225 boxShadow: `0 0 0 2px ${BLUE}`, 226 zIndex: 1000, 227 width: '600px', 228 }} 229 onClick={(e) => e.stopPropagation()} 230 > 231 <h2 style={{ color: WHITE, textAlign: 'center' }}>Metadata Editor</h2> 232 {isLoading ? ( 233 <p>Loading metadata...</p> 234 ) : error ? ( 235 <p style={{ color: RED }}>{error}</p> 236 ) : ( 237 <> 238 {Object.entries(metadata).map(([key, value]) => ( 239 <div key={key} style={{ marginBottom: '10px', display: 'flex', alignItems: 'center', justifyContent: 'center' }}> 240 <label style={{ width: '30%', marginRight: '10px', textAlign: 'right' }}>{key}:</label> 241 {renderInput(key, value)} 242 </div> 243 ))} 244 <div style={{ display: 'flex', justifyContent: 'center', marginTop: '20px' }}> 245 <button 246 onClick={onClose} 247 style={{ 248 marginRight: '10px', 249 padding: '5px 10px', 250 backgroundColor: RED, 251 color: WHITE, 252 border: 'none', 253 borderRadius: '4px', 254 cursor: 'pointer' 255 }} 256 > 257 Cancel 258 </button> 259 <button 260 onClick={handleSave} 261 style={{ 262 padding: '5px 10px', 263 backgroundColor: BLUE, 264 color: WHITE, 265 border: 'none', 266 borderRadius: '4px', 267 cursor: 'pointer' 268 }} 269 > 270 Save 271 </button> 272 </div> 273 </> 274 )} 275 </div> 276 ); 277 }; 278 279 export default MetadataPanel;