sources-content.js (4036B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at <http://mozilla.org/MPL/2.0/>. */ 4 5 /** 6 * Sources content reducer. 7 * 8 * This store the textual content for each source. 9 */ 10 11 import { pending, fulfilled, rejected } from "../utils/async-value"; 12 13 export function initialSourcesContentState() { 14 return { 15 /** 16 * Text content of all the original sources. 17 * This is large data, so this is only fetched on-demand for a subset of sources. 18 * This state attribute is mutable in order to avoid cloning this possibly large map 19 * on each new source. But selectors are never based on the map. Instead they only 20 * query elements of the map. 21 * 22 * Map(source id => AsyncValue<String>) 23 */ 24 mutableOriginalSourceTextContentMapBySourceId: new Map(), 25 26 /** 27 * Text content of all the generated sources. 28 * 29 * Map(source actor is => AsyncValue<String>) 30 */ 31 mutableGeneratedSourceTextContentMapBySourceActorId: new Map(), 32 33 /** 34 * Incremental number that is bumped each time we navigate to a new page. 35 * 36 * This is used to better handle async race condition where we mix previous page data 37 * with the new page. As sources are keyed by URL we may easily conflate the two page loads data. 38 */ 39 epoch: 1, 40 }; 41 } 42 43 function update(state = initialSourcesContentState(), action) { 44 switch (action.type) { 45 case "LOAD_ORIGINAL_SOURCE_TEXT": 46 if (!action.source) { 47 throw new Error("Missing source"); 48 } 49 return updateSourceTextContent(state, action); 50 51 case "LOAD_GENERATED_SOURCE_TEXT": 52 if (!action.sourceActor) { 53 throw new Error("Missing source actor."); 54 } 55 return updateSourceTextContent(state, action); 56 57 case "REMOVE_SOURCES": 58 return removeAllSourceTextContentForSourcesAndActors(state, action); 59 } 60 61 return state; 62 } 63 64 /* 65 * Update a source's loaded text content. 66 */ 67 function updateSourceTextContent(state, action) { 68 // If there was a navigation between the time the action was started and 69 // completed, we don't want to update the store. 70 if (action.epoch !== state.epoch) { 71 return state; 72 } 73 74 let content; 75 if (action.status === "start") { 76 content = pending(); 77 } else if (action.status === "error") { 78 content = rejected(action.error); 79 } else if (typeof action.value.text === "string") { 80 content = fulfilled({ 81 type: "text", 82 value: action.value.text, 83 contentType: action.value.contentType, 84 }); 85 } else { 86 content = fulfilled({ 87 type: "wasm", 88 value: action.value.text, 89 }); 90 } 91 92 if (action.source && action.sourceActor) { 93 throw new Error( 94 "Both the source and the source actor should not exist at the same time" 95 ); 96 } 97 98 if (action.source) { 99 state.mutableOriginalSourceTextContentMapBySourceId.set( 100 action.source.id, 101 content 102 ); 103 } 104 105 if (action.sourceActor) { 106 state.mutableGeneratedSourceTextContentMapBySourceActorId.set( 107 action.sourceActor.id, 108 content 109 ); 110 } 111 112 return { 113 ...state, 114 }; 115 } 116 117 function removeAllSourceTextContentForSourcesAndActors(state, action) { 118 if (!action.sources.length && !action.actors.length) { 119 return state; 120 } 121 const originalSizeBefore = 122 state.mutableOriginalSourceTextContentMapBySourceId.size; 123 for (const source of action.sources) { 124 state.mutableOriginalSourceTextContentMapBySourceId.delete(source.id); 125 } 126 const generatedSizeBefore = 127 state.mutableGeneratedSourceTextContentMapBySourceActorId.size; 128 for (const actor of action.actors) { 129 state.mutableGeneratedSourceTextContentMapBySourceActorId.delete(actor.id); 130 } 131 if ( 132 originalSizeBefore != 133 state.mutableOriginalSourceTextContentMapBySourceId.size || 134 generatedSizeBefore != 135 state.mutableGeneratedSourceTextContentMapBySourceActorId.size 136 ) { 137 return { ...state }; 138 } 139 return state; 140 } 141 142 export default update;