/ src / components / MetadataPanel.jsx
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;