diff --git a/src/components/calculation-history/CalculationHistory.tsx b/src/components/calculation-history/CalculationHistory.tsx
new file mode 100644
index 00000000..adf501a3
--- /dev/null
+++ b/src/components/calculation-history/CalculationHistory.tsx
@@ -0,0 +1,36 @@
+import React from "react";
+import { CalculationData } from "../../data/calculations/ICalculationsRepository";
+type CountryDropdownType = {
+ refreshCalculationHistory: () => void;
+ calculationHistory: CalculationData[];
+};
+export const CalculationHistory = ({
+ refreshCalculationHistory,
+ calculationHistory,
+}: CountryDropdownType) => {
+ return (
+
+
+
+ {calculationHistory.map((calculation, index) => (
+ -
+
Bytes: {calculation.bytes}
+ Emissions: {calculation.emissions}
+
+ Specific Emissions: {calculation.specificEmissions}
+
+
+ {Array.from(calculation.selectedCountries).map(
+ ([countryName, percentage]) => (
+ -
+ {countryName}: {percentage}
+
+ )
+ )}
+
+
+ ))}
+
+
+ );
+};
diff --git a/src/data/calculations/CalculationsRepository.ts b/src/data/calculations/CalculationsRepository.ts
new file mode 100644
index 00000000..2190e2ed
--- /dev/null
+++ b/src/data/calculations/CalculationsRepository.ts
@@ -0,0 +1,66 @@
+import { IStorageRepository } from "../storage/IStorageRepository";
+import {
+ CalculationData,
+ ICalculationsRepository,
+} from "./ICalculationsRepository";
+
+export class CalculationsRepository implements ICalculationsRepository {
+ remoteDataSource: IStorageRepository = IStorageRepository.instance;
+
+ async storeCalculation(calculationData: CalculationData): Promise {
+ const oldCalculations = await this.getAllCalculations();
+ const newCalculations = [calculationData, ...oldCalculations];
+ await this.remoteDataSource.set({
+ allCalculations: JSON.stringify(newCalculations),
+ });
+ }
+
+ async cacheOngoingCalculation(
+ calculationData: CalculationData
+ ): Promise {
+ await this.remoteDataSource.set({
+ ongoingCalculation: JSON.stringify(calculationData),
+ });
+ }
+
+ async clearOngoingCalculation(): Promise {
+ await this.remoteDataSource.set({
+ ongoingCalculation: null,
+ });
+ }
+
+ async getAllCalculations(): Promise {
+ const data = await this.remoteDataSource.get({
+ allCalculations: JSON.stringify([]),
+ });
+
+ return JSON.parse(
+ data["allCalculations"] as string
+ ) as CalculationData[];
+ }
+
+ async _getOngoingCalculation(): Promise {
+ const data = await this.remoteDataSource.get({
+ ongoingCalculation: null,
+ });
+
+ if (data["ongoingCalculation"] !== null) {
+ return JSON.parse(
+ data["ongoingCalculation"] as string
+ ) as CalculationData;
+ }
+ return null;
+ }
+
+ async getLastCalculation(): Promise {
+ const ongoingCalculation = await this._getOngoingCalculation();
+ if (ongoingCalculation !== null) {
+ return ongoingCalculation;
+ }
+ const oldCalculations = await this.getAllCalculations();
+ if (oldCalculations.length > 0) {
+ return oldCalculations[0];
+ }
+ return null;
+ }
+}
diff --git a/src/data/calculations/ICalculationsRepository.ts b/src/data/calculations/ICalculationsRepository.ts
new file mode 100644
index 00000000..678c07ae
--- /dev/null
+++ b/src/data/calculations/ICalculationsRepository.ts
@@ -0,0 +1,42 @@
+import { CountryName } from "../../constants/Countries";
+import { CalculationsRepository } from "./CalculationsRepository";
+import { TestCalculationsRepository } from "./TestCalculationsRepository";
+
+export abstract class ICalculationsRepository {
+ private static _instance: ICalculationsRepository;
+ static get instance(): ICalculationsRepository {
+ if (!this._instance) {
+ switch (process.env.ENV) {
+ case "development":
+ this._instance = new CalculationsRepository();
+ break;
+ case "test":
+ this._instance = new TestCalculationsRepository();
+ break;
+ default:
+ throw new Error(`Unknown environment: ${process.env.ENV}`);
+ }
+ }
+
+ return this._instance;
+ }
+
+ abstract storeCalculation(calculationData: CalculationData): Promise;
+
+ abstract cacheOngoingCalculation(
+ calculationData: CalculationData
+ ): Promise;
+
+ abstract clearOngoingCalculation(): Promise;
+
+ abstract getLastCalculation(): Promise;
+
+ abstract getAllCalculations(): Promise;
+}
+
+export type CalculationData = {
+ bytes: number;
+ emissions: number;
+ specificEmissions: number;
+ selectedCountries: Map;
+};
diff --git a/src/data/calculations/TestCalculationsRepository.ts b/src/data/calculations/TestCalculationsRepository.ts
new file mode 100644
index 00000000..b085f2bc
--- /dev/null
+++ b/src/data/calculations/TestCalculationsRepository.ts
@@ -0,0 +1,38 @@
+import {
+ CalculationData,
+ ICalculationsRepository,
+} from "./ICalculationsRepository";
+
+export class TestCalculationsRepository implements ICalculationsRepository {
+ private _allCalculations: CalculationData[] = [];
+ private _ongoingCalculation: CalculationData | null = null;
+
+ async storeCalculation(calculationData: CalculationData): Promise {
+ const tempArray = [calculationData, ...this._allCalculations];
+ this._allCalculations = tempArray;
+ }
+
+ async cacheOngoingCalculation(
+ calculationData: CalculationData
+ ): Promise {
+ this._ongoingCalculation = calculationData;
+ }
+
+ async clearOngoingCalculation(): Promise {
+ this._ongoingCalculation = null;
+ }
+
+ async getAllCalculations(): Promise {
+ return this._allCalculations;
+ }
+
+ async getLastCalculation(): Promise {
+ if (this._ongoingCalculation !== null) {
+ return this._ongoingCalculation;
+ }
+ if (this._allCalculations.length > 0) {
+ return this._allCalculations[0];
+ }
+ return null;
+ }
+}
diff --git a/src/data/emissions/EmissionsRepository.ts b/src/data/emissions/EmissionsRepository.ts
deleted file mode 100644
index 6df39efd..00000000
--- a/src/data/emissions/EmissionsRepository.ts
+++ /dev/null
@@ -1,34 +0,0 @@
-import { IStorageRepository } from "../storage/IStorageRepository";
-import { EmissionsData, IEmissionsRepository } from "./IEmissionsRepository";
-
-export class EmissionsRepository implements IEmissionsRepository {
- remoteDataSource: IStorageRepository = IStorageRepository.instance;
-
- async storeLastCalculation(emissionsData: EmissionsData): Promise {
- try {
- await this.remoteDataSource.set({
- lastCalculation: JSON.stringify(emissionsData),
- });
- } catch (e: unknown) {
- throw Error(e as string);
- }
- }
-
- async getLastCalculation(): Promise {
- try {
- const data = await this.remoteDataSource.get({
- lastCalculation: JSON.stringify({
- bytes: 0,
- emissions: 0,
- specificEmissions: 0,
- }),
- });
-
- return JSON.parse(
- data["lastCalculation"] as string
- ) as EmissionsData;
- } catch (e: unknown) {
- throw Error(e as string);
- }
- }
-}
diff --git a/src/data/emissions/IEmissionsRepository.ts b/src/data/emissions/IEmissionsRepository.ts
deleted file mode 100644
index 78641133..00000000
--- a/src/data/emissions/IEmissionsRepository.ts
+++ /dev/null
@@ -1,32 +0,0 @@
-import { EmissionsRepository } from "./EmissionsRepository";
-import { TestEmissionsRepository } from "./TestEmissionsRepository";
-
-export abstract class IEmissionsRepository {
- private static _instance: IEmissionsRepository;
- static get instance(): IEmissionsRepository {
- if (!this._instance) {
- switch (process.env.ENV) {
- case "development":
- this._instance = new EmissionsRepository();
- break;
- case "test":
- this._instance = new TestEmissionsRepository();
- break;
- default:
- throw new Error(`Unknown environment: ${process.env.ENV}`);
- }
- }
-
- return this._instance;
- }
-
- abstract storeLastCalculation(emissionsData: EmissionsData): Promise;
-
- abstract getLastCalculation(): Promise;
-}
-
-export type EmissionsData = {
- bytes: number;
- emissions: number;
- specificEmissions: number;
-};
diff --git a/src/data/emissions/TestEmissionsRepository.ts b/src/data/emissions/TestEmissionsRepository.ts
deleted file mode 100644
index dcdaf823..00000000
--- a/src/data/emissions/TestEmissionsRepository.ts
+++ /dev/null
@@ -1,17 +0,0 @@
-import { EmissionsData, IEmissionsRepository } from "./IEmissionsRepository";
-
-export class TestEmissionsRepository implements IEmissionsRepository {
- private _lastCalculation: EmissionsData = {
- bytes: 0,
- emissions: 0,
- specificEmissions: 0,
- };
-
- async storeLastCalculation(emissionsData: EmissionsData): Promise {
- this._lastCalculation = emissionsData;
- }
-
- async getLastCalculation(): Promise {
- return this._lastCalculation;
- }
-}
diff --git a/src/data/storage/IStorageRepository.ts b/src/data/storage/IStorageRepository.ts
index f95dd0f9..4f8757f9 100644
--- a/src/data/storage/IStorageRepository.ts
+++ b/src/data/storage/IStorageRepository.ts
@@ -1,7 +1,7 @@
import { StorageRepository } from "./StorageRepository";
import { TestStorageRepository } from "./TestStorageRepository";
-export type StorageDataType = string | number;
+export type StorageDataType = string | number | null;
export abstract class IStorageRepository {
private static _instance: IStorageRepository;
static get instance(): IStorageRepository {
diff --git a/src/popup.tsx b/src/popup.tsx
index 92dfed11..c019ff95 100644
--- a/src/popup.tsx
+++ b/src/popup.tsx
@@ -3,6 +3,7 @@ import { createRoot } from "react-dom/client";
import { CountryDropdown } from "./components/CountryDropdown";
import { usePopup } from "./usePopup";
import { SelectedCountries } from "./components/selected-countries/SelectedCountries";
+import { CalculationHistory } from "./components/calculation-history/CalculationHistory";
export const Popup = () => {
const {
@@ -15,6 +16,8 @@ export const Popup = () => {
averageSpecificEmissions,
refreshAndGetSize,
stopRecording,
+ refreshCalculationHistory,
+ calculationHistory,
error,
} = usePopup();
@@ -45,6 +48,10 @@ export const Popup = () => {
setCountryPercentage={setCountryPercentage}
/>
+
{error && {error}
}
);
diff --git a/src/usePopup.ts b/src/usePopup.ts
index f0b87bec..5630426f 100644
--- a/src/usePopup.ts
+++ b/src/usePopup.ts
@@ -4,14 +4,17 @@ import { calculateAverageSpecificEmissionsHelper } from "./helpers/calculateAver
import { calculateCarbon } from "./helpers/calculateCarbon";
import { ISelectedCountriesRepository } from "./data/selected_countries/ISelectedCountriesRepository";
import { useMountEffect } from "./helpers/useOnceAfterFirstMount";
-import { IEmissionsRepository } from "./data/emissions/IEmissionsRepository";
+import {
+ CalculationData,
+ ICalculationsRepository,
+} from "./data/calculations/ICalculationsRepository";
import { IBytesRepository } from "./data/bytes/IBytesRepository";
export const usePopup = () => {
const selectedCountriesRepository: ISelectedCountriesRepository =
ISelectedCountriesRepository.instance;
- const emissionsRepository: IEmissionsRepository =
- IEmissionsRepository.instance;
+ const calculationsRepository: ICalculationsRepository =
+ ICalculationsRepository.instance;
const bytesRepository: IBytesRepository = IBytesRepository.instance;
const [totalBytesTransferred, settotalBytesTransferred] = useState(0);
@@ -21,6 +24,15 @@ export const usePopup = () => {
>(new Map());
const [averageSpecificEmissions, setAverageSpecificEmissions] = useState(0);
const [error, setError] = useState();
+ const [calculationHistory, setCalculationHistory] = useState<
+ CalculationData[]
+ >([]);
+
+ const refreshCalculationHistory = async () => {
+ const calculationsData =
+ await calculationsRepository.getAllCalculations();
+ setCalculationHistory(calculationsData);
+ };
const setCountryPercentage = async (
country: CountryName,
@@ -113,6 +125,19 @@ export const usePopup = () => {
}
}
});
+ try {
+ calculationsRepository.storeCalculation({
+ bytes: totalBytesTransferred,
+ emissions: emissions,
+ specificEmissions: averageSpecificEmissions,
+ selectedCountries: selectedCountries,
+ });
+ calculationsRepository.clearOngoingCalculation();
+ } catch (e: unknown) {
+ if (e instanceof Error) {
+ setError(e.message);
+ }
+ }
};
const addSelectedCountry = async (country: CountryName) => {
@@ -142,10 +167,11 @@ export const usePopup = () => {
selectedCountries
);
setEmissions(_emissions);
- emissionsRepository.storeLastCalculation({
+ calculationsRepository.cacheOngoingCalculation({
bytes: changes.totalBytesTransferred.newValue,
emissions: _emissions,
specificEmissions: averageSpecificEmissions,
+ selectedCountries: selectedCountries,
});
}
};
@@ -159,21 +185,28 @@ export const usePopup = () => {
totalBytesTransferredListener
);
};
- }, [selectedCountries, averageSpecificEmissions, emissionsRepository]);
+ }, [selectedCountries, averageSpecificEmissions, calculationsRepository]);
useMountEffect(() => {
- selectedCountriesRepository
- .getSelectedCountriesAndPercentages()
- .then((newMap) => {
- setSelectedCountries(newMap);
- });
+ const getSelectedCountriesAndSetState = async () => {
+ const newMap =
+ await selectedCountriesRepository.getSelectedCountriesAndPercentages();
+ setSelectedCountries(newMap);
+ };
+ getSelectedCountriesAndSetState();
});
+
useMountEffect(() => {
- emissionsRepository.getLastCalculation().then((emissionsData) => {
- settotalBytesTransferred(emissionsData.bytes);
- setEmissions(emissionsData.emissions);
- setAverageSpecificEmissions(emissionsData.specificEmissions);
- });
+ const getLastCalculationAndSetState = async () => {
+ const calculationData =
+ await calculationsRepository.getLastCalculation();
+ settotalBytesTransferred(calculationData?.bytes ?? 0);
+ setEmissions(calculationData?.emissions ?? 0);
+ setAverageSpecificEmissions(
+ calculationData?.specificEmissions ?? 0
+ );
+ };
+ getLastCalculationAndSetState();
});
return {
@@ -186,6 +219,8 @@ export const usePopup = () => {
averageSpecificEmissions,
refreshAndGetSize,
stopRecording,
+ calculationHistory,
+ refreshCalculationHistory,
error,
};
};