/ embark-ui / src / reducers / index.js
index.js
  1  import {combineReducers} from 'redux';
  2  import {REQUEST, SUCCESS, FAILURE, CONTRACT_COMPILE, FILES, LOGOUT, AUTHENTICATE,
  3          FETCH_CREDENTIALS, UPDATE_BASE_ETHER, CHANGE_THEME, FETCH_THEME, EXPLORER_SEARCH, DEBUGGER_INFO,
  4          SIGN_MESSAGE, VERIFY_MESSAGE, TOGGLE_BREAKPOINT,
  5          UPDATE_DEPLOYMENT_PIPELINE, WEB3_CONNECT, WEB3_DEPLOY, WEB3_ESTIMAGE_GAS, FETCH_EDITOR_TABS} from "../actions";
  6  import {EMBARK_PROCESS_NAME, DARK_THEME, DEPLOYMENT_PIPELINES, DEFAULT_HOST} from '../constants';
  7  
  8  const BN_FACTOR = 10000;
  9  const VOID_ADDRESS = '0x0000000000000000000000000000000000000000';
 10  const MAX_ELEMENTS = 200;
 11  
 12  const entitiesDefaultState = {
 13    accounts: [],
 14    blocks: [],
 15    transactions: [],
 16    processes: [],
 17    services: [],
 18    processLogs: [],
 19    commandSuggestions: [],
 20    contracts: [],
 21    contractProfiles: [],
 22    contractFunctions: [],
 23    contractDeploys: [],
 24    contractCompiles: [],
 25    contractLogs: [],
 26    contractEvents: [],
 27    messages: [],
 28    messageChannels: [],
 29    versions: [],
 30    plugins: [],
 31    ensRecords: [],
 32    files: [],
 33    gasOracleStats: [],
 34  };
 35  
 36  const sorter = {
 37    blocks: function(a, b) {
 38      return b.number - a.number;
 39    },
 40    transactions: function(a, b) {
 41      return ((BN_FACTOR * b.blockNumber) + b.transactionIndex) - ((BN_FACTOR * a.blockNumber) + a.transactionIndex);
 42    },
 43    processes: function(a, b) {
 44      if (a.name === EMBARK_PROCESS_NAME) return -1;
 45      if (b.name === EMBARK_PROCESS_NAME) return 1;
 46      return 0;
 47    },
 48    commandSuggestions: function(a, b) {
 49      if (a.value.indexOf('.').length > 0) {
 50        let a_levels = a.value.split('.').length;
 51        let b_levels = b.value.split('.').length;
 52        let diff = b_levels - a_levels;
 53        if (diff !== 0) return diff * -1;
 54      }
 55      let lengthDiff = b.value.length - a.value.length;
 56      if (lengthDiff !== 0) return lengthDiff * -1;
 57      return 0;
 58    },
 59    processLogs: function(a, b) {
 60      if (a.name !== b.name) {
 61        if(a.name < b.name) return -1;
 62        if(a.name > b.name) return 1;
 63        return 0;
 64      }
 65  
 66      if (a.id === undefined && b.id === undefined) {
 67        return b.timestamp - a.timestamp;
 68      }
 69  
 70      return b.id - a.id;
 71    },
 72    contractLogs: function(a, b) {
 73      return a.timestamp - b.timestamp;
 74    },
 75    messages: function(a, b) {
 76      return a.time - b.time;
 77    },
 78    files: function(a, b) {
 79      if (a.name < b.name) return -1;
 80      if (a.name > b.name) return 1;
 81      return 0;
 82    },
 83  };
 84  
 85  const filtrer = {
 86    processes: function(process, index, self) {
 87      if (["embark", "blockchain"].indexOf(process.name) === -1) return false;
 88      return index === self.findIndex((t) => t.name === process.name);
 89    },
 90    services: function(process, index, self) {
 91      return index === self.findIndex((t) => t.name === process.name);
 92    },
 93    processLogs: function(processLog, index, self) {
 94      if (processLog.id !== undefined) {
 95        return index === self.findIndex((p) => p.id === processLog.id) && index <= MAX_ELEMENTS;
 96      }
 97      return true;
 98    },
 99    contracts: function(contract, index, self) {
100      return index === self.findIndex((t) => t.className === contract.className);
101    },
102    commandSuggestions: function(command, index, self) {
103      return index === self.findIndex((c) => (
104        command.value === c.value
105      ));
106    },
107    accounts: function(account, index, self) {
108      return index === self.findIndex((t) => t.address === account.address);
109    },
110    blocks: function(block, index, self) {
111      if (index > MAX_ELEMENTS) {
112        return false;
113      }
114  
115      return index === self.findIndex((t) => t.number === block.number);
116    },
117    transactions: function(tx, index, self) {
118      if (index > MAX_ELEMENTS) {
119        return false;
120      }
121      return index === self.findIndex((t) => (
122        t.blockNumber === tx.blockNumber && t.transactionIndex === tx.transactionIndex
123      ));
124    },
125    ensRecords: function(record, index, self) {
126      return record.name && record.address && record.address !== VOID_ADDRESS && index === self.findIndex((r) => (
127        r.address === record.address && r.name === record.name
128      ));
129    },
130    files: function(file, index, self) {
131      return index === self.findIndex((f) => (
132        file.name === f.name
133      ));
134    },
135    gasOracleStats: function(stat, index, _self) {
136      return index === 0; // Only keep last one
137    },
138    versions: function(version, index, self) {
139      return index === self.findIndex((v) => v.value === version.value && v.name === version.name);
140    }
141  };
142  
143  function entities(state = entitiesDefaultState, action) {
144    if (action.type === FILES[SUCCESS]) {
145      return {...state, files: action.files};
146    }
147    for (let name of Object.keys(state)) {
148      let filter = filtrer[name] || (() => true);
149      let sort = sorter[name] || (() => true);
150      if (action[name] && action[name].length > 1) {
151        return {...state, [name]: [...action[name], ...state[name]].sort(sort).filter(filter)};
152      }
153      if (action[name] && action[name].length === 1) {
154        let entity = action[name][0];
155        let nested = Object.keys(state).reduce((acc, entityName) => {
156          if (entity && entity[entityName] && entity[entityName].length > 0) {
157            let entityFilter = filtrer[entityName] || (() => true);
158            let entitySort = sorter[entityName] || (() => true);
159            acc[entityName] = [...entity[entityName], ...state[entityName]].sort(entitySort).filter(entityFilter);
160          }
161          return acc;
162        }, {});
163        return {
164          ...state, ...nested, [name]: [...action[name], ...state[name]].sort(sort).filter(filter)
165        };
166      }
167    }
168  
169    return state;
170  }
171  
172  function errorMessage(_state = null, action) {
173    return action.error || null;
174  }
175  
176  function errorEntities(state = {}, action) {
177    if (!action.type.endsWith(SUCCESS)) {
178      return state;
179    }
180    let newState = {};
181    for (let name of Object.keys(entitiesDefaultState)) {
182      if (action[name] && action[name].length > 0 && action[name][0]) {
183        newState[name] = action[name][0].error;
184      }
185    }
186    return {...state, ...newState};
187  }
188  
189  function loading(_state = false, action) {
190    return action.type.endsWith(REQUEST);
191  }
192  
193  function compilingContract(state = false, action) {
194    if (action.type === CONTRACT_COMPILE[REQUEST]) {
195      return true;
196    } else if (action.type === CONTRACT_COMPILE[FAILURE] || action.type === CONTRACT_COMPILE[SUCCESS]) {
197      return false;
198    }
199  
200    return state;
201  }
202  
203  const DEFAULT_CREDENTIALS_STATE = {
204    host: DEFAULT_HOST,
205    token: '',
206    authenticated: false,
207    authenticating: false,
208    error: null
209  };
210  
211  function credentials(state = DEFAULT_CREDENTIALS_STATE, action) {
212    if (action.type === LOGOUT[SUCCESS]) {
213      return DEFAULT_CREDENTIALS_STATE;
214    }
215  
216    if (action.type === AUTHENTICATE[FAILURE]) {
217      return {error: action.error, ...DEFAULT_CREDENTIALS_STATE};
218    }
219  
220    if (action.type === AUTHENTICATE[SUCCESS]) {
221      return {...state, ...{authenticated: true, authenticating: false, token: action.token, host: action.host, error: null}};
222    }
223  
224    if (action.type === FETCH_CREDENTIALS[SUCCESS]) {
225      return {...state, ...{token: action.token, host: action.host}};
226    }
227  
228    if (action.type === AUTHENTICATE[REQUEST]) {
229      return {...state, ...{authenticating: true, error: null}};
230    }
231  
232    return state;
233  }
234  
235  function baseEther(state = '1', action) {
236    if (action.type === UPDATE_BASE_ETHER) {
237      return action.payload;
238    }
239    return state;
240  }
241  
242  function theme(state=DARK_THEME, action) {
243    if (action.type === CHANGE_THEME[REQUEST] || (action.type === FETCH_THEME[SUCCESS] && action.theme)) {
244      return action.theme;
245    }
246    return state
247  }
248  
249  function deploymentPipeline(state = DEPLOYMENT_PIPELINES.embark, action) {
250    if (action.type === UPDATE_DEPLOYMENT_PIPELINE) {
251      return action.payload;
252    }
253    return state;
254  }
255  
256  function searchResult(state = {}, action) {
257    if (action.type === EXPLORER_SEARCH[SUCCESS]) {
258      return action.searchResult;
259    }
260    if (action.type === EXPLORER_SEARCH[REQUEST]) {
261      return {};
262    }
263    return state;
264  }
265  
266  const DEFAULT_MESSAGE_SIGNATURE_STATE = {
267    pending: false,
268    error: null,
269    payload: null
270  };
271  
272  function messageSignature(state = DEFAULT_MESSAGE_SIGNATURE_STATE, action) {
273  
274    if (action.type === SIGN_MESSAGE[REQUEST]) {
275      return {...state, pending: true, error: null, payload: null };
276    }
277  
278    if (action.type === SIGN_MESSAGE[FAILURE]) {
279      return {...state, pending: false, error: action.signMessageError };
280    }
281  
282    if (action.type === SIGN_MESSAGE[SUCCESS]) {
283      return {...state, ...{
284        pending: false,
285        error: null,
286        payload: {
287          signature: action.signature,
288          message: action.message,
289          signer: action.signer
290        }
291      }};
292    }
293  
294    return state;
295  }
296  
297  const DEFAULT_MESSAGE_VERIFICATION_STATE = {
298    pending: false,
299    error: null,
300    payload: null
301  };
302  
303  function messageVerification(state = DEFAULT_MESSAGE_VERIFICATION_STATE, action) {
304    if (action.type === VERIFY_MESSAGE[REQUEST]) {
305      return {...state, pending: true, error: null, payload: null };
306    }
307  
308    if (action.type === VERIFY_MESSAGE[FAILURE]) {
309      return {...state, pending: false, error: action.verifyMessageError };
310    }
311  
312    if (action.type === VERIFY_MESSAGE[SUCCESS]) {
313      return {...state, ...{
314        pending: false,
315        error: null,
316        payload: {
317          verifiedAddress: action.address
318        }
319      }};
320    }
321    return state;
322  }
323  
324  function breakpoints(state = {}, action) {
325    if (action.type === TOGGLE_BREAKPOINT[SUCCESS]) {
326      const {filename, lineNumber} = action.payload;
327      let lineNumbers = state[filename] || [];
328      if (lineNumbers.includes(lineNumber)){
329        lineNumbers = lineNumbers.filter(ln => ln !== lineNumber);
330      } else {
331        lineNumbers.push(lineNumber);
332      }
333      return {...state, [filename]: lineNumbers};
334    }
335  
336    return state;
337  }
338  
339  function web3(state = {deployments: {}, gasEstimates: {}}, action) {
340    if (action.type === WEB3_CONNECT[SUCCESS]) {
341      return {...state, instance: action.web3};
342    } else if (action.type === WEB3_DEPLOY[REQUEST]) {
343      return {...state, deployments: {...state['deployments'], [action.contract.className]: {running: true, error: null}}};
344    } else if (action.type === WEB3_DEPLOY[SUCCESS]){
345      return {...state, deployments: {...state['deployments'], [action.contract.className]: {...action.receipt, running: false, error: null}}};
346    } else if (action.type === WEB3_DEPLOY[FAILURE]){
347      return {...state, deployments: {...state['deployments'], [action.contract.className]: {error: action.web3Error, running: false}}};
348    } else if (action.type === WEB3_ESTIMAGE_GAS[REQUEST]){
349      return {...state, gasEstimates: {...state['gasEstimates'], [action.contract.className]: {running: true, error: null}}};
350    } else if (action.type === WEB3_ESTIMAGE_GAS[SUCCESS]){
351      return {...state, gasEstimates: {...state['gasEstimates'], [action.contract.className]: {gas: action.gas, running: false, error: null}}};
352    } else if (action.type === WEB3_ESTIMAGE_GAS[FAILURE]){
353      return {...state, gasEstimates: {...state['gasEstimates'], [action.contract.className]: {error: action.web3Error, running: false}}};
354    }
355  
356    return state
357  }
358  
359  function debuggerInfo(state={}, action) {
360    if (action.type === DEBUGGER_INFO[SUCCESS]) {
361      return action.data;
362    }
363    return state;
364  }
365  
366  function editorTabs(state = [], action) {
367    if (action.type === FETCH_EDITOR_TABS[SUCCESS] && action.editorTabs) {
368      return action.editorTabs;
369    }
370    return state;
371  }
372  
373  const rootReducer = combineReducers({
374    entities,
375    loading,
376    compilingContract,
377    errorMessage,
378    errorEntities,
379    credentials,
380    baseEther,
381    searchResult,
382    messageSignature,
383    messageVerification,
384    breakpoints,
385    deploymentPipeline,
386    web3,
387    debuggerInfo,
388    theme,
389    editorTabs
390  });
391  
392  export default rootReducer;