Skip to content

Commit

Permalink
feat: added ability for users to select initial cube by default (#330)
Browse files Browse the repository at this point in the history
* feat: allow users to select a default starting cube

* fix: updating settings if they differ from the old ones

* fix: refactor styles, highlight selected cube, and close select on outside click

* refactor styles to match menu schema.

---------

Co-authored-by: Bryan Lundberg <bryanlundberg@outlook.com>
  • Loading branch information
myheadisempty and bryanlundberg authored Jul 15, 2024
1 parent 26dde3b commit a3dacbd
Show file tree
Hide file tree
Showing 10 changed files with 178 additions and 9 deletions.
5 changes: 4 additions & 1 deletion messages/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,8 @@
"confirm": "Confirm",
"cancel": "Cancel",
"save": "Save",
"delete": "Delete"
"delete": "Delete",
"none": "None"
},
"Settings-menu": {
"title": "Settings",
Expand All @@ -116,6 +117,8 @@
"allowed-file-types": "Only image files (JPEG, PNG, GIF) are allowed.",
"max-file-size": "Maximum file size allowed is",
"data": "App Data",
"preferences": "Preferences",
"default-cube": "Default cube",
"import-from-file": "Import from file",
"export-to-file": "Export to file",
"about": "About",
Expand Down
5 changes: 4 additions & 1 deletion messages/ru.json
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,8 @@
"confirm": "Подтвердить",
"cancel": "Отмена",
"save": "Сохранить",
"delete": "Удалить"
"delete": "Удалить",
"none": "Нет"
},
"Settings-menu": {
"title": "Настройки",
Expand All @@ -111,6 +112,8 @@
"best-average": "Лучшая средняя",
"worst-time": "Худшее время",
"theme": "Тема",
"preferences": "Предпочтения",
"default-cube": "Кубик по умолчанию",
"custom-background-image": "Пользовательское фоновое изображение",
"format": "Формат",
"allowed-file-types": "Разрешены только файлы изображений (JPEG, PNG, GIF).",
Expand Down
2 changes: 1 addition & 1 deletion src/components/Select.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ export default function Select() {
);
}

function MiniatureIcon({ category }: { category: Categories }) {
export function MiniatureIcon({ category }: { category: Categories }) {
const images = cubeCollection.map((option) => {
if (option.name === category) {
return (
Expand Down
12 changes: 10 additions & 2 deletions src/components/menu-settings/Menu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,14 @@ import MenuSelectLanguage from "./MenuSelectLanguage";
import {
ArrowLeftIcon,
BellAlertIcon,
ClockIcon,
CogIcon,
CpuChipIcon,
FolderIcon,
IdentificationIcon,
ShieldCheckIcon,
SparklesIcon,
ViewColumnsIcon,
} from "@heroicons/react/24/solid";
import MenuSelectDefaultStartCube from "./MenuSelectDefaultStartCube";

export default function MenuSettings() {
const { settingsOpen, setSettingsOpen, settings } = useSettingsModalStore();
Expand Down Expand Up @@ -139,6 +139,14 @@ export default function MenuSettings() {
>
<DataImportExport />
</MenuSection>

<MenuSection
icon={<ViewColumnsIcon className="w-6 h-6" />}
title={t("preferences")}
>
<MenuSelectDefaultStartCube />
</MenuSection>

<MenuSection
icon={<IdentificationIcon className="w-6 h-6" />}
title={t("about")}
Expand Down
97 changes: 97 additions & 0 deletions src/components/menu-settings/MenuSelectDefaultStartCube.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import { useTranslations } from "next-intl";
import { useRef, useState } from "react";
import { useSettingsModalStore } from "@/store/SettingsModalStore";
import useClickOutside from "@/hooks/useClickOutside";
import { useTimerStore } from "@/store/timerStore";
import { MiniatureIcon } from "../Select";
import { AnimatePresence, motion } from "framer-motion";
import loadSettings from "@/lib/loadSettings";
import { Cube } from "@/interfaces/Cube";

export default function MenuSelectDefaultStartCube() {
const { settings, setSettings } = useSettingsModalStore();
const t = useTranslations("Index");

const [open, setOpen] = useState(false);
const { cubes } = useTimerStore();
const componentRef = useRef<HTMLButtonElement | null>(null);

const handleClose = () => {
setOpen(false);
};

const handleCubeSelect = (cube: Cube | null) => {
const currentSettings = loadSettings();
currentSettings.preferences.defaultCube.cube = cube;
setSettings(currentSettings);
window.localStorage.setItem("settings", JSON.stringify(currentSettings));
handleClose();
};

const defaultCube = settings.preferences.defaultCube.cube;

useClickOutside(componentRef, handleClose);

return (
<div className="flex justify-between items-center mb-1 w-full">
<div className="ms-12">{t("Settings-menu.default-cube")}</div>
<div className="relative me-6 w-fit mx-auto">
<button
onClick={() => setOpen(!open)}
ref={componentRef}
className={`text-md border rounded-md transition duration-200 hover:bg-neutral-200 text-neutral-950 hover:border-neutral-400 h-10 px-2 w-40
${
open
? "border-neutral-400 bg-neutral-200"
: "bg-neutral-50 border-neutral-200 "
}`}
>
<div className="flex items-center gap-2">
{defaultCube && <MiniatureIcon category={defaultCube.category} />}
<div className="truncate" title={defaultCube?.name}>
{defaultCube ? defaultCube.name : t("Inputs.none")}
</div>
</div>
</button>
<AnimatePresence>
{open && (
<motion.div
initial={{ y: 0, scale: 0.9, opacity: 0.8 }}
animate={{ y: 0, scale: 1, opacity: 1 }}
exit={{ x: 0, scale: 0.9, opacity: 0 }}
id="list-options"
className="absolute overflow-auto max-h-[400px] p-1 top-10 mt-1 right-0 w-full h-auto border rounded-md bg-neutral-200 border-neutral-400 text-neutral-900"
>
<div
onClick={() => handleCubeSelect(null)}
className={`cursor-pointer transition duration-200 p-1 select-none rounded-md ps-2 flex overflow-hidden ${
defaultCube === null
? "bg-neutral-700 text-neutral-50"
: "text-neutral-900 hover:bg-neutral-500 hover:text-neutral-100"
}`}
>
<div className="overflow-hidden">{t("Inputs.none")}</div>
</div>
{cubes?.map((cube) => (
<div
key={cube.id}
onClick={() => handleCubeSelect(cube)}
className={`cursor-pointer transition duration-200 p-1 select-none rounded-md ps-2 flex overflow-hidden ${
defaultCube?.id === cube.id
? "bg-neutral-700 text-neutral-50"
: "text-neutral-900 hover:bg-neutral-500 hover:text-neutral-100"
}`}
>
<div className="flex justify-start gap-3">
<MiniatureIcon category={cube.category} />
<div className="overflow-hidden">{cube.name}</div>
</div>
</div>
))}
</motion.div>
)}
</AnimatePresence>
</div>
</div>
);
}
23 changes: 23 additions & 0 deletions src/hooks/useModalCube.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import { DeleteCubeDetails } from "@/interfaces/DeleteCubeDetails";
import formatTime from "@/lib/formatTime";
import useEscape from "./useEscape";
import { deleteCubeById, getAllCubes, saveCube } from "@/db/dbOperations";
import loadSettings from "@/lib/loadSettings";
import { useSettingsModalStore } from "@/store/SettingsModalStore";

export default function useModalCube() {
const {
Expand All @@ -20,6 +22,8 @@ export default function useModalCube() {
setCubeName,
} = useCubesModalStore();

const { setSettings } = useSettingsModalStore();

const {
setCubes,
setSelectedCube,
Expand All @@ -33,6 +37,8 @@ export default function useModalCube() {
const [showDeleteConfirmation, setShowDeleteConfirmation] = useState(false);
const [cubeData, setCubeData] = useState<DeleteCubeDetails | null>(null);

const defaultCubeId = loadSettings().preferences.defaultCube.cube?.id;

useEscape(() => setModalOpen(false));

const handleClickRadio = (category: Categories) => {
Expand Down Expand Up @@ -88,6 +94,16 @@ export default function useModalCube() {
}
}

if (editingCube.id === defaultCubeId) {
const currentSettings = loadSettings();
if (currentSettings.preferences.defaultCube.cube) {
currentSettings.preferences.defaultCube.cube.name = name;
currentSettings.preferences.defaultCube.cube.category = category;
}
setSettings(currentSettings);
window.localStorage.setItem("settings", JSON.stringify(currentSettings));
}

const updatedCube = await saveCube({
...editingCube,
name: name.trim(),
Expand Down Expand Up @@ -153,6 +169,13 @@ export default function useModalCube() {
setTimerStatistics();
}

if (editingCube.id === defaultCubeId) {
const currentSettings = loadSettings();
currentSettings.preferences.defaultCube.cube = null;
setSettings(currentSettings);
window.localStorage.setItem("settings", JSON.stringify(currentSettings));
}

await deleteCubeById(editingCube.id);
const cubes = await getAllCubes();
setCubes(cubes);
Expand Down
7 changes: 6 additions & 1 deletion src/hooks/usePreloadSettings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,18 @@ import { getAllCubes } from "@/db/dbOperations";
import { useBackgroundImageStore } from "@/store/BackgroundThemeStore";

export function usePreloadSettings() {
const { setCubes } = useTimerStore();
const { setCubes, setSelectedCube, setTimerStatistics, setNewScramble } =
useTimerStore();
const { setSettings } = useSettingsModalStore();
const { setBackgroundImage } = useBackgroundImageStore();

useEffect(() => {
const getSettings = loadSettings();
const defaultCube = getSettings.preferences.defaultCube.cube;
setSelectedCube(defaultCube);
setSettings(getSettings);
setTimerStatistics();
setNewScramble(defaultCube);
}, [setSettings]);

useEffect(() => {
Expand Down
6 changes: 6 additions & 0 deletions src/interfaces/Settings.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { Cube } from "./Cube";
import { Themes } from "./types/Themes";

interface Timer {
Expand Down Expand Up @@ -26,9 +27,14 @@ interface Theme {
content: { color: string; key: string };
}

interface Preferences {
defaultCube: { cube: Cube | null; key: string };
}

export interface Settings {
timer: Timer;
features: Features;
alerts: Alerts;
theme: Theme;
preferences: Preferences;
}
3 changes: 3 additions & 0 deletions src/lib/const/defaultSettings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,7 @@ export const defaultSettings: Settings = {
background: { color: "dark", key: "background-color" },
content: { color: "dark", key: "letter-color" },
},
preferences: {
defaultCube: { cube: null, key: "default-cube" },
},
};
27 changes: 24 additions & 3 deletions src/lib/loadSettings.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,20 @@
import { Settings } from "@/interfaces/Settings";
import { defaultSettings } from "./const/defaultSettings";

function areSettingsDifferent(
currentSettings: Settings,
defaultSettings: Settings
) {
return JSON.stringify(currentSettings) !== JSON.stringify(defaultSettings);
}

function mergeSettings(currentSettings: Settings, defaultSettings: Settings) {
return {
...defaultSettings,
...currentSettings,
};
}

/**
* Retrieves the user settings from local storage.
* If no settings are found, it initializes and saves the default settings.
Expand All @@ -20,7 +34,14 @@ export default function loadSettings(): Settings {
return defaultSettings;
}

// Parse and return the retrieved settings
const settings = JSON.parse(data);
return settings;
const currentSettings: Settings = JSON.parse(data);

if (areSettingsDifferent(currentSettings, defaultSettings)) {
const updatedSettings = mergeSettings(currentSettings, defaultSettings);
window.localStorage.setItem("settings", JSON.stringify(updatedSettings));

return updatedSettings;
}

return currentSettings;
}

0 comments on commit a3dacbd

Please sign in to comment.