feat(app): add persistence of settings across sessions

This commit is contained in:
rektdeckard
2021-11-26 22:03:54 -05:00
parent cdcf38466e
commit 14d8c9d0e7
8 changed files with 140 additions and 10 deletions

View File

@@ -8,12 +8,14 @@ import Footer from "../Footer/Footer";
import ErrorBoundary from "../ErrorBoundary/ErrorBoundary";
import Notice from "../Notice/Notice";
import useIconParameters from "../../hooks/useIconParameters";
import usePersistSettings from "../../hooks/usePersistSettings";
const errorFallback = <Notice message="Search error" />;
const waitingFallback = <Notice type="none" message="" />;
const App: React.FC<any> = () => {
useIconParameters();
usePersistSettings();
return (
<React.StrictMode>

View File

@@ -0,0 +1,13 @@
.action-button {
background-color: rgba(255, 255, 255, 0.05);
color: white;
padding: 8px;
border-radius: 8px;
cursor: pointer;
}
@media screen and (max-width: 558px) {
.action-button {
display: none;
}
}

View File

@@ -0,0 +1,53 @@
import React from "react";
import { ArrowCounterClockwise, CheckCircle, Link } from "phosphor-react";
import { useRecoilValue, useResetRecoilState } from "recoil";
import { iconWeightAtom, iconSizeAtom, iconColorAtom } from "../../state/atoms";
import "./SettingsActions.css";
import useTransientState from "../../hooks/useTransientState";
import { resetSettingsSelector } from "../../state/selectors";
const SettingsActions: React.FC = () => {
const weight = useRecoilValue(iconWeightAtom);
const size = useRecoilValue(iconSizeAtom);
const color = useRecoilValue(iconColorAtom);
const reset = useResetRecoilState(resetSettingsSelector);
const [copied, setCopied] = useTransientState<boolean>(false, 2000);
const copyDeepLinkToClipboard = () => {
const paramString = new URLSearchParams([
["weight", weight.toString()],
["size", size.toString()],
["color", color.replace("#", "")],
]).toString();
void navigator.clipboard?.writeText(
`${window.location.host}?${paramString}`
);
setCopied(true);
};
return (
<>
<button
className="action-button"
title="Restore default settings"
onClick={reset}
>
<ArrowCounterClockwise size={24} />
</button>
<button
className="action-button"
title="Copy URL for current settings"
onClick={copyDeepLinkToClipboard}
>
{copied ? (
<CheckCircle size={24} color="#1FA647" weight="fill" />
) : (
<Link size={24} />
)}
</button>
</>
);
};
export default SettingsActions;

View File

@@ -5,6 +5,7 @@ import StyleInput from "../StyleInput/StyleInput";
import SearchInput from "../SearchInput/SearchInput";
import SizeInput from "../SizeInput/SizeInput";
import ColorInput from "../ColorInput/ColorInput";
import SettingsActions from "../SettingsActions/SettingsActions";
type ToolbarProps = {};
@@ -16,6 +17,7 @@ const Toolbar: React.FC<ToolbarProps> = () => {
<SearchInput />
<SizeInput />
<ColorInput />
<SettingsActions />
</div>
</nav>
);

View File

@@ -34,4 +34,29 @@ export default () => {
if (normalizedColor.isValid()) setColor(normalizedColor.toHexString());
}
}, [color, setColor]);
useEffect(() => {
if (!weight && !size && !color) {
const persistedState = JSON.parse(
window.localStorage.getItem("__phosphor_settings__") || "null"
);
if (!!persistedState) {
const { weight, size, color } = persistedState;
if (weight) {
if (weight.toUpperCase() in IconStyle) setWeight(weight as IconStyle);
}
if (size) {
const normalizedSize = parseInt(size);
if (typeof normalizedSize === "number" && isFinite(normalizedSize))
setSize(Math.min(Math.max(normalizedSize, 16), 96));
}
if (color) {
const normalizedColor = TinyColor(color);
if (normalizedColor.isValid())
setColor(normalizedColor.toHexString());
}
}
}
}, []);
};

View File

@@ -0,0 +1,18 @@
import { useRecoilValue } from "recoil";
import useDebounce from "./useDebounce";
import { iconWeightAtom, iconSizeAtom, iconColorAtom } from "../state/atoms";
export default function usePersistSettings() {
const weight = useRecoilValue(iconWeightAtom);
const size = useRecoilValue(iconSizeAtom);
const color = useRecoilValue(iconColorAtom);
useDebounce(
() => {
const serializedState = JSON.stringify({ weight, size, color });
window.localStorage.setItem("__phosphor_settings__", serializedState);
},
2000,
[weight, size, color]
);
}

View File

@@ -13,7 +13,7 @@ export const iconWeightAtom = atom<IconStyle>({
export const iconSizeAtom = atom<number>({
key: "iconSizeAtom",
default: 48,
default: 32,
});
export const iconColorAtom = atom<string>({

View File

@@ -2,7 +2,12 @@ import { selector, selectorFamily } from "recoil";
import TinyColor from "tinycolor2";
import Fuse from "fuse.js";
import { searchQueryAtom, iconColorAtom } from "./atoms";
import {
searchQueryAtom,
iconWeightAtom,
iconSizeAtom,
iconColorAtom,
} from "./atoms";
import { IconEntry, IconCategory } from "../lib";
import { icons } from "../lib/icons";
@@ -52,17 +57,29 @@ export const singleCategoryQueryResultsSelector = selectorFamily<
IconCategory
>({
key: "singleCategoryQueryResultsSelector",
get: (category: IconCategory) => ({ get }) => {
const filteredResults = get(filteredQueryResultsSelector);
return new Promise((resolve) =>
resolve(
filteredResults.filter((icon) => icon.categories.includes(category))
)
);
},
get:
(category: IconCategory) =>
({ get }) => {
const filteredResults = get(filteredQueryResultsSelector);
return new Promise((resolve) =>
resolve(
filteredResults.filter((icon) => icon.categories.includes(category))
)
);
},
});
export const isDarkThemeSelector = selector<boolean>({
key: "isDarkThemeSelector",
get: ({ get }) => TinyColor(get(iconColorAtom)).isLight(),
});
export const resetSettingsSelector = selector<null>({
key: "resetSettings",
get: () => null,
set: ({ reset }) => {
reset(iconWeightAtom);
reset(iconSizeAtom);
reset(iconColorAtom);
},
});