Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

FIX-2121 Add wrapper around localStorage #2133

Merged
merged 6 commits into from
Nov 21, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 0 additions & 9 deletions Src/WitsmlExplorer.Frontend/components/Constants.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,3 @@ export const DateFormat = {

export const MILLIS_IN_SECOND = 1000;
export const SECONDS_IN_MINUTE = 60;

export const STORAGE_THEME_KEY = "selectedTheme";
export const STORAGE_TIMEZONE_KEY = "selectedTimeZone";
export const STORAGE_MODE_KEY = "selectedMode";
export const STORAGE_FILTER_HIDDENOBJECTS_KEY = "hiddenObjects";
export const STORAGE_MISSING_DATA_AGENT_CHECKS_KEY = "missingDataAgentChecks";
export const STORAGE_DATETIMEFORMAT_KEY = "selectedDateTimeFormat";
export const STORAGE_QUERYVIEW_DATA = "queryViewData";
export const STORAGE_DECIMAL_KEY = "decimalPrefernce";
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import ServerService from "../../services/serverService";
import WellService from "../../services/wellService";
import { Colors } from "../../styles/Colors";
import Icon from "../../styles/Icons";
import { STORAGE_FILTER_HIDDENOBJECTS_KEY } from "../Constants";
import { STORAGE_FILTER_HIDDENOBJECTS_KEY, getLocalStorageItem } from "../../tools/localStorageHelpers";
import ServerModal, { showDeleteServerModal } from "../Modals/ServerModal";
import UserCredentialsModal, { UserCredentialsModalProps } from "../Modals/UserCredentialsModal";

Expand Down Expand Up @@ -92,8 +92,8 @@ const ServerManager = (): React.ReactElement => {

const updateVisibleObjects = (supportedObjects: string[]) => {
const updatedVisibility = { ...allVisibleObjects };
const hiddenItems = localStorage.getItem(STORAGE_FILTER_HIDDENOBJECTS_KEY)?.split(",") || [];
hiddenItems.forEach((objectType) => (updatedVisibility[objectType as ObjectType] = VisibilityStatus.Hidden));
const hiddenItems = getLocalStorageItem<ObjectType[]>(STORAGE_FILTER_HIDDENOBJECTS_KEY, { defaultValue: [] });
hiddenItems.forEach((objectType) => (updatedVisibility[objectType] = VisibilityStatus.Hidden));
Object.values(ObjectType)
.filter((objectType) => !supportedObjects.map((o) => o.toLowerCase()).includes(objectType.toLowerCase()))
.forEach((objectType) => (updatedVisibility[objectType] = VisibilityStatus.Disabled));
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { Checkbox, IconButton, useTheme } from "@material-ui/core";
import { ColumnDef, Row, SortingFn, Table } from "@tanstack/react-table";
import { useMemo, useContext } from "react";
import { useContext, useMemo } from "react";
import OperationContext from "../../../contexts/operationContext";
import { DecimalPreference } from "../../../contexts/operationStateReducer";
import Icon from "../../../styles/Icons";
import { getFromStorage, orderingStorageKey, widthsStorageKey } from "./contentTableStorage";
import { STORAGE_CONTENTTABLE_ORDER_KEY, STORAGE_CONTENTTABLE_WIDTH_KEY, getLocalStorageItem } from "../../../tools/localStorageHelpers";
import { activeId, calculateColumnWidth, componentSortingFn, expanderId, measureSortingFn, selectId, toggleRow } from "./contentTableUtils";
import { ContentTableColumn, ContentType } from "./tableParts";
import OperationContext from "../../../contexts/operationContext";
import { DecimalPreference } from "../../../contexts/operationStateReducer";

declare module "@tanstack/react-table" {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
Expand All @@ -24,7 +24,7 @@ export const useColumnDef = (viewId: string, columns: ContentTableColumn[], inse
} = useContext(OperationContext);

return useMemo(() => {
const savedWidths = getFromStorage(viewId, widthsStorageKey);
const savedWidths = getLocalStorageItem<{ [label: string]: number }>(viewId + STORAGE_CONTENTTABLE_WIDTH_KEY);
let columnDef: ColumnDef<any, any>[] = columns.map((column) => {
return {
id: column.label,
Expand All @@ -39,7 +39,7 @@ export const useColumnDef = (viewId: string, columns: ContentTableColumn[], inse
};
});

const savedOrder = getFromStorage(viewId, orderingStorageKey);
const savedOrder = getLocalStorageItem<string[]>(viewId + STORAGE_CONTENTTABLE_ORDER_KEY);
if (savedOrder) {
const sortedColumns = savedOrder.flatMap((label) => {
const foundColumn = columnDef.find((col) => col.id == label);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@ import { Table } from "@tanstack/react-table";
import { useContext, useState } from "react";
import styled from "styled-components";
import OperationContext from "../../../contexts/operationContext";
import { useLocalStorageState } from "../../../hooks/useLocalStorageState";
import { Colors } from "../../../styles/Colors";
import { orderingStorageKey, removeFromStorage, saveToStorage } from "./contentTableStorage";
import { STORAGE_CONTENTTABLE_ORDER_KEY, removeLocalStorageItem } from "../../../tools/localStorageHelpers";
import { calculateColumnWidth, expanderId, selectId } from "./contentTableUtils";
import { ContentTableColumn, ContentType } from "./tableParts";

Expand All @@ -28,6 +29,7 @@ export const ColumnOptionsMenu = (props: {
const [draggedOverId, setDraggedOverId] = useState<string | null>();
const [isMenuOpen, setIsMenuOpen] = useState<boolean>(false);
const [menuAnchor, setMenuAnchor] = useState<HTMLButtonElement | null>(null);
const [, saveOrderToStorage] = useLocalStorageState<string[]>(viewId + STORAGE_CONTENTTABLE_ORDER_KEY);
const isCompactMode = useTheme().props.MuiCheckbox?.size === "small";

const drop = (e: React.DragEvent<HTMLDivElement>) => {
Expand All @@ -43,7 +45,7 @@ export const ColumnOptionsMenu = (props: {
order.push(draggedId);
}
table.setColumnOrder(order);
saveToStorage(viewId, orderingStorageKey, order);
if (viewId) saveOrderToStorage(order);
}
setDraggedId(null);
setDraggedOverId(null);
Expand All @@ -56,7 +58,7 @@ export const ColumnOptionsMenu = (props: {
order[index] = order[index - 1];
order[index - 1] = columnId;
table.setColumnOrder(order);
saveToStorage(viewId, orderingStorageKey, order);
if (viewId) saveOrderToStorage(order);
}
};

Expand All @@ -67,7 +69,7 @@ export const ColumnOptionsMenu = (props: {
order[index] = order[index + 1];
order[index + 1] = columnId;
table.setColumnOrder(order);
saveToStorage(viewId, orderingStorageKey, order);
if (viewId) saveOrderToStorage(order);
}
};

Expand Down Expand Up @@ -145,7 +147,7 @@ export const ColumnOptionsMenu = (props: {
<ResetButton
onClick={() => {
table.setColumnOrder([...(checkableRows ? [selectId] : []), ...(expandableRows ? [expanderId] : []), ...columns.map((column) => column.label)]);
removeFromStorage(viewId, orderingStorageKey);
if (viewId) removeLocalStorageItem(viewId + STORAGE_CONTENTTABLE_ORDER_KEY);
}}
>
Reset ordering
Expand Down
Original file line number Diff line number Diff line change
@@ -1,63 +1,24 @@
import { Table, VisibilityState } from "@tanstack/react-table";
import { useEffect } from "react";

export const widthsStorageKey = "-widths";
const hiddenStorageKey = "-hidden";
export const orderingStorageKey = "-ordering";
type StorageKey = typeof widthsStorageKey | typeof hiddenStorageKey | typeof orderingStorageKey;
type StorageKeyToPreference = {
[widthsStorageKey]: { [label: string]: number };
[hiddenStorageKey]: string[];
[orderingStorageKey]: string[];
};

export function saveToStorage<Key extends StorageKey>(viewId: string | null, storageKey: Key, preference: StorageKeyToPreference[Key]): void {
try {
if (viewId != null) {
localStorage.setItem(viewId + storageKey, JSON.stringify(preference));
}
} catch {
// disregard unavailable local storage
}
}

export function getFromStorage<Key extends StorageKey>(viewId: string | null, storageKey: Key): StorageKeyToPreference[Key] | null {
try {
if (viewId != null) {
return JSON.parse(localStorage.getItem(viewId + storageKey));
}
} catch {
return null;
}
}

export function removeFromStorage<Key extends StorageKey>(viewId: string | null, storageKey: Key): void {
try {
if (viewId != null) {
localStorage.removeItem(viewId + storageKey);
}
} catch {
// disregard unavailable local storage
}
}
import { useLocalStorageState } from "../../../hooks/useLocalStorageState";
import { STORAGE_CONTENTTABLE_HIDDEN_KEY, STORAGE_CONTENTTABLE_WIDTH_KEY, getLocalStorageItem } from "../../../tools/localStorageHelpers";

export const useStoreWidthsEffect = (viewId: string | null, table: Table<any>) => {
const [, setWidths] = useLocalStorageState<{ [label: string]: number }>(viewId + STORAGE_CONTENTTABLE_WIDTH_KEY);
useEffect(() => {
const dispatch = setTimeout(() => {
if (viewId != null) {
const widths = Object.assign({}, ...table.getLeafHeaders().map((header) => ({ [header.id]: header.getSize() })));
saveToStorage(viewId, widthsStorageKey, widths);
}
}, 400);
return () => clearTimeout(dispatch);
if (viewId) {
const widths = Object.assign({}, ...table.getLeafHeaders().map((header) => ({ [header.id]: header.getSize() })));
if (viewId) setWidths(widths);
}
}, [table.getTotalSize()]);
};

export const useStoreVisibilityEffect = (viewId: string | null, columnVisibility: VisibilityState) => {
const [, setVisibility] = useLocalStorageState<string[]>(viewId + STORAGE_CONTENTTABLE_HIDDEN_KEY);
useEffect(() => {
if (viewId != null) {
if (viewId) {
const hiddenColumns = Object.entries(columnVisibility).flatMap(([columnId, isVisible]) => (isVisible ? [] : columnId));
saveToStorage(viewId, hiddenStorageKey, hiddenColumns);
if (viewId) setVisibility(hiddenColumns);
}
}, [columnVisibility]);
};
Expand All @@ -66,6 +27,6 @@ export const initializeColumnVisibility = (viewId: string | null) => {
if (viewId == null) {
return {};
}
const hiddenColumns = getFromStorage(viewId, hiddenStorageKey);
const hiddenColumns = getLocalStorageItem<string[]>(viewId + STORAGE_CONTENTTABLE_HIDDEN_KEY);
return hiddenColumns == null ? {} : Object.assign({}, ...hiddenColumns.map((hiddenColumn) => ({ [hiddenColumn]: false })));
};
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
import { Accordion, Autocomplete, Button, Icon, Typography } from "@equinor/eds-core-react";
import { CloudUpload } from "@material-ui/icons";
import { useContext, useEffect, useRef, useState } from "react";
import { useContext, useRef, useState } from "react";
import styled from "styled-components";
import { v4 as uuid } from "uuid";
import OperationContext from "../../contexts/operationContext";
import OperationType from "../../contexts/operationType";
import useExport from "../../hooks/useExport";
import { useLocalStorageState } from "../../hooks/useLocalStorageState";
import MissingDataJob, { MissingDataCheck } from "../../models/jobs/missingDataJob";
import WellReference from "../../models/jobs/wellReference";
import WellboreReference from "../../models/jobs/wellboreReference";
import { ObjectType } from "../../models/objectType";
import JobService, { JobType } from "../../services/jobService";
import { Colors } from "../../styles/Colors";
import { STORAGE_MISSING_DATA_AGENT_CHECKS_KEY } from "../Constants";
import { STORAGE_MISSING_DATA_AGENT_CHECKS_KEY } from "../../tools/localStorageHelpers";
import { StyledAccordionHeader } from "./LogComparisonModal";
import { objectToProperties, selectAllProperties } from "./MissingDataAgentProperties";
import ModalDialog, { ModalContentLayout, ModalWidth } from "./ModalDialog";
Expand All @@ -31,21 +32,15 @@ const MissingDataAgentModal = (props: MissingDataAgentModalProps): React.ReactEl
dispatchOperation,
operationState: { colors }
} = useContext(OperationContext);
const [missingDataChecks, setMissingDataChecks] = useState<MissingDataCheck[]>([{ id: uuid() } as MissingDataCheck]);
const [missingDataChecks, setMissingDataChecks] = useLocalStorageState<MissingDataCheck[]>(STORAGE_MISSING_DATA_AGENT_CHECKS_KEY, {
defaultValue: [{ id: uuid() } as MissingDataCheck],
valueVerifier: verifyObjectIsChecks,
storageTransformer: (checks) => checks.map((check) => ({ ...check, id: uuid() }))
});
const [errors, setErrors] = useState<string[]>([]);
const { exportData, exportOptions } = useExport();
const inputFileRef = useRef<HTMLInputElement>(null);

useEffect(() => {
const checkString = localStorage.getItem(STORAGE_MISSING_DATA_AGENT_CHECKS_KEY);
const checks = stringToChecks(checkString);
if (checks.length > 0) setMissingDataChecks(checks);
}, []);

useEffect(() => {
localStorage.setItem(STORAGE_MISSING_DATA_AGENT_CHECKS_KEY, JSON.stringify(missingDataChecks));
}, [missingDataChecks]);

const stringToChecks = (checkString: string): MissingDataCheck[] => {
try {
const checksObj = JSON.parse(checkString);
Expand All @@ -56,19 +51,6 @@ const MissingDataAgentModal = (props: MissingDataAgentModalProps): React.ReactEl
}
};

const verifyObjectIsChecks = (obj: any): boolean => {
if (!Array.isArray(obj)) return false;
return obj.every(
(check) =>
typeof check === "object" &&
(!("objectType" in check) || missingDataObjectOptions.includes(check.objectType)) &&
(!("properties" in check) ||
("objectType" in check &&
Array.isArray(check.properties) &&
check.properties.every((property: any) => typeof property === "string" && objectToProperties[check.objectType].includes(property))))
);
};

const validateChecks = (): boolean => {
const updatedErrors = [];

Expand Down Expand Up @@ -233,6 +215,19 @@ const MissingDataAgentModal = (props: MissingDataAgentModalProps): React.ReactEl
);
};

const verifyObjectIsChecks = (obj: any): boolean => {
if (!Array.isArray(obj)) return false;
return obj.every(
(check) =>
typeof check === "object" &&
(!("objectType" in check) || missingDataObjectOptions.includes(check.objectType)) &&
(!("properties" in check) ||
("objectType" in check &&
Array.isArray(check.properties) &&
check.properties.every((property: any) => typeof property === "string" && objectToProperties[check.objectType].includes(property))))
);
};

export default MissingDataAgentModal;

const CheckLayout = styled.div`
Expand Down
15 changes: 7 additions & 8 deletions Src/WitsmlExplorer.Frontend/components/Modals/SettingsModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { getAccountInfo, msalEnabled, signOut } from "../../msal/MsalAuthProvide
import AuthorizationService from "../../services/authorizationService";
import { dark, light } from "../../styles/Colors";
import Icon from "../../styles/Icons";
import { STORAGE_DATETIMEFORMAT_KEY, STORAGE_DECIMAL_KEY, STORAGE_MODE_KEY, STORAGE_THEME_KEY, STORAGE_TIMEZONE_KEY } from "../Constants";
import { STORAGE_DATETIMEFORMAT_KEY, STORAGE_DECIMAL_KEY, STORAGE_MODE_KEY, STORAGE_THEME_KEY, STORAGE_TIMEZONE_KEY, setLocalStorageItem } from "../../tools/localStorageHelpers";
import { getOffsetFromTimeZone } from "../DateFormatter";
import { StyledNativeSelect } from "../Select";
import ModalDialog from "./ModalDialog";
Expand All @@ -30,15 +30,14 @@ const SettingsModal = (): React.ReactElement => {
operationState: { theme, timeZone, colors, dateTimeFormat, decimals },
dispatchOperation
} = useContext(OperationContext);

const [checkedDecimalPreference, setCheckedDecimalPreference] = useState<string>(() => {
return decimals === DecimalPreference.Raw ? DecimalPreference.Raw : DecimalPreference.Decimal;
});
const [decimalError, setDecimalError] = useState<boolean>(false);

const onChangeTheme = (event: any) => {
const selectedTheme = event.target.value;
localStorage.setItem(STORAGE_THEME_KEY, selectedTheme);
setLocalStorageItem<UserTheme>(STORAGE_THEME_KEY, selectedTheme);
dispatchOperation({ type: OperationType.SetTheme, payload: selectedTheme });
};
const onChangeMode = (event: any) => {
Expand All @@ -48,27 +47,27 @@ const SettingsModal = (): React.ReactElement => {
} else {
selectedMode = dark;
}
localStorage.setItem(STORAGE_MODE_KEY, event.target.value);
setLocalStorageItem<"light" | "dark">(STORAGE_MODE_KEY, event.target.value);
dispatchOperation({ type: OperationType.SetMode, payload: selectedMode });
};

const onChangeDateTimeFormat = (event: any) => {
const selectedDateTimeFormat = event.target.value;
localStorage.setItem(STORAGE_DATETIMEFORMAT_KEY, selectedDateTimeFormat);
setLocalStorageItem<DateTimeFormat>(STORAGE_DATETIMEFORMAT_KEY, selectedDateTimeFormat);
dispatchOperation({ type: OperationType.SetDateTimeFormat, payload: selectedDateTimeFormat });
};

const onChangeTimeZone = (event: any) => {
const selectedTimeZone = event.target.value;
localStorage.setItem(STORAGE_TIMEZONE_KEY, selectedTimeZone);
setLocalStorageItem<TimeZone>(STORAGE_TIMEZONE_KEY, selectedTimeZone);
dispatchOperation({ type: OperationType.SetTimeZone, payload: selectedTimeZone });
};

const onChangeDecimals = (event: any) => {
const inputDecimals: any = parseInt(event.target.value, 10);
if (!isNaN(inputDecimals) && inputDecimals >= 0 && inputDecimals <= 10) {
setDecimalError(false);
localStorage.setItem(STORAGE_DECIMAL_KEY, inputDecimals);
setLocalStorageItem<DecimalPreference>(STORAGE_DECIMAL_KEY, inputDecimals);
dispatchOperation({ type: OperationType.SetDecimal, payload: inputDecimals });
} else {
setDecimalError(true);
Expand All @@ -78,7 +77,7 @@ const SettingsModal = (): React.ReactElement => {
const onChangeDecimalPreference = (event: React.ChangeEvent<HTMLInputElement>) => {
const selectedValue = event.target.value;
if (event.target.value === DecimalPreference.Raw) {
localStorage.setItem(STORAGE_DECIMAL_KEY, DecimalPreference.Raw);
setLocalStorageItem<DecimalPreference>(STORAGE_DECIMAL_KEY, DecimalPreference.Raw);
dispatchOperation({ type: OperationType.SetDecimal, payload: DecimalPreference.Raw as DecimalPreference });
}
setCheckedDecimalPreference(selectedValue);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import NavigationType from "../../contexts/navigationType";
import OperationContext from "../../contexts/operationContext";
import { ObjectType } from "../../models/objectType";
import { Colors } from "../../styles/Colors";
import { STORAGE_FILTER_HIDDENOBJECTS_KEY } from "../Constants";
import { STORAGE_FILTER_HIDDENOBJECTS_KEY, setLocalStorageItem } from "../../tools/localStorageHelpers";

const FilterPanel = (): React.ReactElement => {
const { navigationState, dispatchNavigation } = useContext(NavigationContext);
Expand All @@ -25,12 +25,11 @@ const FilterPanel = (): React.ReactElement => {
} else {
updatedVisibility[objectType] = VisibilityStatus.Visible;
}
localStorage.setItem(
setLocalStorageItem<ObjectType[]>(
STORAGE_FILTER_HIDDENOBJECTS_KEY,
Object.entries(updatedVisibility)
.filter(([, value]) => value == VisibilityStatus.Hidden)
.map(([key]) => key)
.join(",")
.map(([key]) => key as ObjectType)
);
updateSelectedFilter({ objectVisibilityStatus: updatedVisibility });
};
Expand Down
Loading