/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at . */
/**
* Sources content reducer.
*
* This store the textual content for each source.
*/
import { pending, fulfilled, rejected } from "../utils/async-value";
export function initialSourcesContentState() {
return {
/**
* Text content of all the original sources.
* This is large data, so this is only fetched on-demand for a subset of sources.
* This state attribute is mutable in order to avoid cloning this possibly large map
* on each new source. But selectors are never based on the map. Instead they only
* query elements of the map.
*
* Map(source id => AsyncValue)
*/
mutableOriginalSourceTextContentMapBySourceId: new Map(),
/**
* Text content of all the generated sources.
*
* Map(source actor is => AsyncValue)
*/
mutableGeneratedSourceTextContentMapBySourceActorId: new Map(),
/**
* Incremental number that is bumped each time we navigate to a new page.
*
* This is used to better handle async race condition where we mix previous page data
* with the new page. As sources are keyed by URL we may easily conflate the two page loads data.
*/
epoch: 1,
};
}
function update(state = initialSourcesContentState(), action) {
switch (action.type) {
case "LOAD_ORIGINAL_SOURCE_TEXT":
if (!action.source) {
throw new Error("Missing source");
}
return updateSourceTextContent(state, action);
case "LOAD_GENERATED_SOURCE_TEXT":
if (!action.sourceActor) {
throw new Error("Missing source actor.");
}
return updateSourceTextContent(state, action);
case "REMOVE_THREAD":
return removeThread(state, action);
}
return state;
}
/*
* Update a source's loaded text content.
*/
function updateSourceTextContent(state, action) {
// If there was a navigation between the time the action was started and
// completed, we don't want to update the store.
if (action.epoch !== state.epoch) {
return state;
}
let content;
if (action.status === "start") {
content = pending();
} else if (action.status === "error") {
content = rejected(action.error);
} else if (typeof action.value.text === "string") {
content = fulfilled({
type: "text",
value: action.value.text,
contentType: action.value.contentType,
});
} else {
content = fulfilled({
type: "wasm",
value: action.value.text,
});
}
if (action.source && action.sourceActor) {
throw new Error(
"Both the source and the source actor should not exist at the same time"
);
}
if (action.source) {
state.mutableOriginalSourceTextContentMapBySourceId.set(
action.source.id,
content
);
}
if (action.sourceActor) {
state.mutableGeneratedSourceTextContentMapBySourceActorId.set(
action.sourceActor.id,
content
);
}
return {
...state,
};
}
function removeThread(state, action) {
const originalSizeBefore =
state.mutableOriginalSourceTextContentMapBySourceId.size;
for (const source of action.sources) {
state.mutableOriginalSourceTextContentMapBySourceId.delete(source.id);
}
const generatedSizeBefore =
state.mutableGeneratedSourceTextContentMapBySourceActorId.size;
for (const actor of action.actors) {
state.mutableGeneratedSourceTextContentMapBySourceActorId.delete(actor.id);
}
if (
originalSizeBefore !=
state.mutableOriginalSourceTextContentMapBySourceId.size ||
generatedSizeBefore !=
state.mutableGeneratedSourceTextContentMapBySourceActorId.size
) {
return { ...state };
}
return state;
}
export default update;