Refactoring theme files

This commit is contained in:
Sean Edmunds
2025-07-28 19:57:06 -05:00
parent 8fcf9a19f2
commit b4e4f14310
33 changed files with 211 additions and 288 deletions

18
package-lock.json generated
View File

@@ -18,8 +18,8 @@
"@tanstack/react-query-persist-client": "^5.83.0",
"idb-keyval": "^6.2.2",
"jotai": "^2.12.5",
"react": "^19.1.0",
"react-dom": "^19.1.0",
"react": "^19.1.1",
"react-dom": "^19.1.1",
"react-icons": "^5.5.0",
"react-router-dom": "^7.7.1",
"recharts": "^3.1.0"
@@ -3562,24 +3562,24 @@
"license": "MIT"
},
"node_modules/react": {
"version": "19.1.0",
"resolved": "https://registry.npmjs.org/react/-/react-19.1.0.tgz",
"integrity": "sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==",
"version": "19.1.1",
"resolved": "https://registry.npmjs.org/react/-/react-19.1.1.tgz",
"integrity": "sha512-w8nqGImo45dmMIfljjMwOGtbmC/mk4CMYhWIicdSflH91J9TyCyczcPFXJzrZ/ZXcgGRFeP6BU0BEJTw6tZdfQ==",
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/react-dom": {
"version": "19.1.0",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.0.tgz",
"integrity": "sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==",
"version": "19.1.1",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.1.tgz",
"integrity": "sha512-Dlq/5LAZgF0Gaz6yiqZCf6VCcZs1ghAJyrsu84Q/GT0gV+mCxbfmKNoGRKBYMJ8IEdGPqu49YWXD02GCknEDkw==",
"license": "MIT",
"dependencies": {
"scheduler": "^0.26.0"
},
"peerDependencies": {
"react": "^19.1.0"
"react": "^19.1.1"
}
},
"node_modules/react-icons": {

View File

@@ -20,8 +20,8 @@
"@tanstack/react-query-persist-client": "^5.83.0",
"idb-keyval": "^6.2.2",
"jotai": "^2.12.5",
"react": "^19.1.0",
"react-dom": "^19.1.0",
"react": "^19.1.1",
"react-dom": "^19.1.1",
"react-icons": "^5.5.0",
"react-router-dom": "^7.7.1",
"recharts": "^3.1.0"

View File

@@ -19,6 +19,25 @@ import Search from './components/Search/Search';
import SettingsButton from './components/SettingsButton/SettingsButton';
export function Homepage() {
const {
appFilter,
bookmarkFilter,
isMobile,
setCurrentUser,
currentLocation,
setCurrentLocation,
} = useCommonHooks();
const dateOptions: Intl.DateTimeFormatOptions = !isMobile
? {
weekday: 'long',
year: 'numeric',
month: 'long',
day: 'numeric',
}
: {};
const date = new Date().toLocaleDateString('en-US', dateOptions);
const { data: personalData, isFetched: personalIsFetched } = useQueryAPI({
endpoint: 'api/personalData',
});
@@ -29,58 +48,6 @@ export function Homepage() {
} = useQueryAPI({
endpoint: 'api/displayedGreeting',
});
const {
appFilter,
bookmarkFilter,
isMobile,
setCurrentUser,
currentLocation,
setCurrentLocation,
} = useCommonHooks();
const [personal, setPersonal] = useState<PersonalData>();
const [message, setMessage] = useState<Message>();
const [displayedGreeting, setDisplayedGreeting] = useState<string | undefined>('');
const [weatherModalOpened, { open: openWeatherModal, close: closeWeatherModal }] =
useDisclosure(false);
const dateOptions: Intl.DateTimeFormatOptions = !isMobile
? {
weekday: 'long',
year: 'numeric',
month: 'long',
day: 'numeric',
}
: {};
const date = new Date().toLocaleDateString('en-US', dateOptions);
useEffect(() => {
if (messageData && isMessagesFetched) {
setMessage(messageData[0]);
}
}, [messageData, isMessagesFetched]);
useEffect(() => {
if (personalData && personalIsFetched) {
setCurrentUser(personalData[0]);
setPersonal(personalData[0]);
}
}, [personalData, personalIsFetched]);
useEffect(() => {
if (personal) {
setDisplayedGreeting(() => {
if (Math.floor(Math.random() * 100) > 10 || message === undefined) {
switch (Math.floor(new Date().getHours() / 6)) {
case 0:
return `Good night, ${personal?.firstName}!`;
case 1:
return `Good morning, ${personal?.firstName}!`;
case 2:
return `Good afternoon, ${personal?.firstName}!`;
case 3:
return `Good evening, ${personal?.firstName}!`;
}
}
return message?.message;
});
}
}, [message, personal, isMessagesFetching]);
const { data: weatherMetadata } = useOSMAPI({
address: personalData?.[0]?.physicalAddress,
});
@@ -112,6 +79,10 @@ export function Homepage() {
x: points?.properties.gridX ?? currentLocation.x,
y: points?.properties.gridY ?? currentLocation.y,
});
const [personal, setPersonal] = useState<PersonalData>();
const [message, setMessage] = useState<Message>();
const [displayedGreeting, setDisplayedGreeting] = useState<string | undefined>('');
const [current, setCurrent] = useState<CW>({
icon: '',
textDescription: '',
@@ -126,7 +97,6 @@ export function Homepage() {
windDirection: 0,
pressure: 0,
});
const [extendedForecast, setExtendedForecast] = useState<EF[]>([
{
detailedForecast: '',
@@ -137,6 +107,39 @@ export function Homepage() {
temperatureUnit: 'F',
},
]);
const [weatherModalOpened, { open: openWeatherModal, close: closeWeatherModal }] =
useDisclosure(false);
useEffect(() => {
if (messageData && isMessagesFetched) {
setMessage(messageData[0]);
}
}, [messageData, isMessagesFetched]);
useEffect(() => {
if (personalData && personalIsFetched) {
setCurrentUser(personalData[0]);
setPersonal(personalData[0]);
}
}, [personalData, personalIsFetched]);
useEffect(() => {
if (personal) {
setDisplayedGreeting(() => {
if (Math.floor(Math.random() * 100) > 10 || message === undefined) {
switch (Math.floor(new Date().getHours() / 6)) {
case 0:
return `Good night, ${personal?.firstName}!`;
case 1:
return `Good morning, ${personal?.firstName}!`;
case 2:
return `Good afternoon, ${personal?.firstName}!`;
case 3:
return `Good evening, ${personal?.firstName}!`;
}
}
return message?.message;
});
}
}, [message, personal, isMessagesFetching]);
useEffect(() => {
if (weather !== undefined && basicWeather !== undefined) {
setCurrent({
@@ -221,9 +224,6 @@ export function Homepage() {
greet={displayedGreeting}
date={date}
current={current}
setCurrent={setCurrent}
weather={weather}
basicWeather={basicWeather}
openWeatherModal={openWeatherModal}
/>
<Applications contentType={appFilter} />
@@ -232,13 +232,7 @@ export function Homepage() {
) : (
<>
<Stack align='center'>
<LargeHeader
greet={displayedGreeting}
date={date}
weather={weather}
basicWeather={basicWeather}
setCurrent={setCurrent}
/>
<LargeHeader greet={displayedGreeting} date={date} />
<Grid grow columns={24} pos={'absolute'} bottom={'1rem'} w={'50%'}>
<CurrentWeather current={current} />
</Grid>

View File

@@ -15,7 +15,8 @@ function Applications({ contentType }: { contentType: string[] }) {
useEffect(() => {
if (applicationData && !isApplicationDataFetching) {
setApplications(
applicationData.sort((a: Application, b: Application) => {
applicationData
.sort((a: Application, b: Application) => {
switch (appSort) {
case 'A>Z':
return a.name > b.name ? 1 : -1;
@@ -34,10 +35,15 @@ function Applications({ contentType }: { contentType: string[] }) {
case 'Custom':
return a.index > b.index ? 1 : -1;
}
}),
})
?.filter(
(application: Application) =>
contentType.includes(application.filterName) || contentType.includes('All'),
),
);
}
}, [applicationData, isApplicationDataFetching, appSort]);
}, [applicationData, isApplicationDataFetching, appSort, contentType]);
return (
<>
{applications.length > 0 && contentType.length > 0 && (
@@ -60,13 +66,7 @@ function Applications({ contentType }: { contentType: string[] }) {
gridTemplateColumns: `repeat(${appDensity}, 1fr)`,
}}
>
{applications
?.filter(
(application: Application) =>
contentType.includes(application.filterName) ||
contentType.includes('All'),
)
.map((application: Application) => {
{applications.map((application: Application) => {
return (
<AppItem
key={application.name}

View File

@@ -28,7 +28,8 @@ function Bookmarks({ contentType }: { contentType: string[] }) {
return category;
});
setGroups(
groupList.sort((a: BookmarkGroup, b: BookmarkGroup) => {
groupList
.sort((a: BookmarkGroup, b: BookmarkGroup) => {
switch (bookmarkSort) {
case 'A>Z':
return a.categoryName > b.categoryName ? 1 : -1;
@@ -47,10 +48,14 @@ function Bookmarks({ contentType }: { contentType: string[] }) {
case 'Custom':
return a.categoryIndex > b.categoryIndex ? 1 : -1;
}
}),
})
?.filter(
(category: BookmarkGroup) =>
contentType.includes(category.filterName) || contentType.includes('All'),
),
);
}
}, [groupData, groupsIsFetched, bookmarkSort]);
}, [groupData, groupsIsFetched, bookmarkSort, contentType]);
return (
<>
@@ -79,21 +84,13 @@ function Bookmarks({ contentType }: { contentType: string[] }) {
gridTemplateColumns: `repeat(${bookmarkDensity}, 1fr)`,
}}
>
{groups
?.filter(
(category: BookmarkGroup) =>
contentType.includes(category.filterName) ||
contentType.includes('All'),
)
.map((category: BookmarkGroup) => {
{groups.map((category: BookmarkGroup) => {
return (
<BookmarkGroups
key={category.categoryName}
category={category.categoryName}
bookmarks={bookmarks.filter(
(bookmark: Bookmark) =>
bookmark.category === category.categoryName,
(bookmark: Bookmark) => bookmark.category === category.categoryName,
)}
bookmarkScale={bookmarkScale}
/>

View File

@@ -8,17 +8,11 @@ function Header({
greet,
date,
current,
setCurrent,
weather,
basicWeather,
openWeatherModal,
}: {
greet: string | undefined;
date: string;
current: CurrentWeather;
setCurrent: (current: CurrentWeather) => void;
weather: any;
basicWeather: any;
openWeatherModal: () => void;
}) {
const [time, setTime] = useState(new Date());
@@ -30,43 +24,6 @@ function Header({
setTime(newDate);
}, 1000);
}, []);
useEffect(() => {
if (weather !== undefined && basicWeather !== undefined) {
setCurrent({
icon: weather.features[0]?.properties.icon?.replace('medium', 'large') ?? '',
textDescription: weather.features[0]?.properties.textDescription,
temperature: Math.round(
(weather.features[0]?.properties.temperature.value * 9) / 5 + 32,
),
highTemp: Math.round(
(basicWeather.properties.maxTemperature.values[0].value * 9) / 5 + 32,
),
lowTemp: Math.round(
(basicWeather.properties.minTemperature.values[0].value * 9) / 5 + 32,
),
precipitation:
weather.features[0]?.properties.precipitationLastHour.value ?? 'N/A',
humidity: Math.round(weather.features[0]?.properties.relativeHumidity.value),
heatIndex:
basicWeather.properties.heatIndex?.values[0].value === null
? null
: Math.round(
(basicWeather.properties.heatIndex.values[0].value * 9) / 5 + 32,
),
windChill:
basicWeather.properties.windChill?.values[0].value === null
? null
: Math.round(
(basicWeather.properties.windChill.values[0].value * 9) / 5 + 32,
),
windSpeed: Math.round(weather.features[0]?.properties.windSpeed.value / 1.60934),
windDirection: weather.features[0]?.properties.windDirection.value,
pressure: Math.round(
weather.features[0]?.properties.barometricPressure.value / 3386.38,
),
});
}
}, [weather, basicWeather]);
return (
<Stack

View File

@@ -2,21 +2,8 @@ import { Stack, Text } from '@mantine/core';
import { useCommonHooks } from 'hooks';
import { useEffect, useState } from 'react';
import { textClasses } from 'styles';
import type { CurrentWeather as CW } from 'types';
function LargeHeader({
greet,
date,
setCurrent,
weather,
basicWeather,
}: {
greet: string | undefined;
date: string;
weather: any;
basicWeather: any;
setCurrent: (current: CW) => void;
}) {
function LargeHeader({ greet, date }: { greet: string | undefined; date: string }) {
const [time, setTime] = useState(new Date());
const { isMobile, currentUser, fixDigits, theme } = useCommonHooks();
@@ -27,44 +14,6 @@ function LargeHeader({
}, 1000);
}, []);
useEffect(() => {
if (weather !== undefined && basicWeather !== undefined) {
setCurrent({
icon: weather.features[0]?.properties.icon?.replace('medium', 'large') ?? '',
textDescription: weather.features[0]?.properties.textDescription,
temperature: Math.round(
(weather.features[0]?.properties.temperature.value * 9) / 5 + 32,
),
highTemp: Math.round(
(basicWeather.properties.maxTemperature.values[0].value * 9) / 5 + 32,
),
lowTemp: Math.round(
(basicWeather.properties.minTemperature.values[0].value * 9) / 5 + 32,
),
precipitation:
weather.features[0]?.properties.precipitationLastHour.value ?? 'N/A',
humidity: Math.round(weather.features[0]?.properties.relativeHumidity.value),
heatIndex:
basicWeather.properties.heatIndex?.values[0].value === null
? null
: Math.round(
(basicWeather.properties.heatIndex.values[0].value * 9) / 5 + 32,
),
windChill:
basicWeather.properties.windChill?.values[0].value === null
? null
: Math.round(
(basicWeather.properties.windChill.values[0].value * 9) / 5 + 32,
),
windSpeed: Math.round(weather.features[0]?.properties.windSpeed.value / 1.60934),
windDirection: weather.features[0]?.properties.windDirection.value,
pressure: Math.round(
weather.features[0]?.properties.barometricPressure.value / 3386.38,
),
});
}
}, [weather, basicWeather]);
return (
<Stack gap={0} pos={'absolute'} align='center' justify='center' top={0}>
<Text

View File

@@ -21,10 +21,10 @@ import {
scrollAreaClasses,
textInputClasses,
} from 'styles';
import { AddBackground } from './AddBackground';
import { BackgroundLightbox } from './BackgroundLightbox';
import { DeleteBackground } from './DeleteBackground';
import { EditBackground } from './EditBackground';
import { AddBackground } from './components/AddBackground';
import { BackgroundLightbox } from './components/BackgroundLightbox';
import { DeleteBackground } from './components/DeleteBackground';
import { EditBackground } from './components/EditBackground';
export function BackgroundModal({
backgroundModalOpened,

View File

@@ -1,5 +1 @@
export { AddBackground } from './AddBackground';
export { BackgroundLightbox } from './BackgroundLightbox';
export { BackgroundModal } from './BackgroundModal';
export { DeleteBackground } from './DeleteBackground';
export { EditBackground } from './EditBackground';

View File

@@ -6,8 +6,8 @@ import { useEffect, useState } from 'react';
import { FiChevronRight } from 'react-icons/fi';
import { textClasses } from 'styles';
import type { Theme, ThemePlaylist } from 'types';
import { ThemePlaylistCard } from './ThemePlaylistCard';
import { ThemePlaylistModal } from './ThemePlaylistModal';
import { ThemePlaylistCard } from './components/ThemePlaylistCard';
import { ThemePlaylistModal } from './components/ThemePlaylistModal';
export function ThemePlaylists() {
const themesEndpoint = 'api/themes';

View File

@@ -14,7 +14,7 @@ import { useState } from 'react';
import { FiEdit, FiTrash } from 'react-icons/fi';
import { scrollAreaClasses } from 'styles';
import type { Theme, ThemePlaylist } from 'types';
import { ThemeMiniCard } from '../Themes';
import { ThemeMiniCard } from '../../Themes';
export function ThemePlaylistCard({
cardPlaylist,

View File

@@ -3,7 +3,8 @@ import { useCommonHooks } from 'hooks';
import { useEffect, useState } from 'react';
import { modalClasses, textInputClasses } from 'styles';
import type { Theme, ThemePlaylist } from 'types';
import { ThemePreviewMiddleware, TimeOfDay } from '../Themes';
import { ThemePreviewMiddleware } from '../../Themes';
import { TimeOfDay } from './TimeOfDay';
export function ThemePlaylistModal({
opened,

View File

@@ -1,3 +1 @@
export { ThemePlaylistCard } from './ThemePlaylistCard';
export { ThemePlaylistModal } from './ThemePlaylistModal';
export { ThemePlaylists } from './ThemePlaylists';

View File

@@ -8,7 +8,9 @@ import {
import { DeleteItemModal } from 'components';
import { useCommonHooks } from 'hooks';
import type { Theme, ThemePlaylist } from 'types';
import { MiniAppItem, MiniGroupItem, ThemeManagementPill } from '.';
import { MiniAppItem } from './components/MiniAppItem';
import { MiniGroupItem } from './components/MiniGroupItem';
import { ThemeManagementPill } from './components/ThemeManagementPill';
export function ThemeCard({
cardTheme,

View File

@@ -0,0 +1 @@
export { ThemeCard } from './ThemeCard';

View File

@@ -4,8 +4,8 @@ import { useCommonHooks, useMutationAPI } from 'hooks';
import { useEffect, useState } from 'react';
import { modalClasses, textInputClasses } from 'styles';
import type { Theme } from 'types';
import { NewThemeStarter } from './NewThemeStarter';
import { ThemeEditor } from './ThemeEditor';
import { NewThemeStarter } from './components/NewThemeStarter';
import { ThemeEditor } from './components/ThemeEditor';
export function ThemeModal({
opened,

View File

@@ -4,9 +4,9 @@ import { useState } from 'react';
import { FaArrowRight } from 'react-icons/fa';
import { textClasses } from 'styles';
import type { Theme } from 'types';
import { ThemeCard } from '../ThemeCard';
import { ThemeMiniCard } from '../ThemeMiniCard';
import { ThemePreviewMiddleware } from '../ThemePreviewMiddleware';
import { ThemeCard } from '../../ThemeCard';
import { ThemeMiniCard } from '../../ThemeMiniCard';
import { ThemePreviewMiddleware } from '../../ThemePreviewMiddleware';
export function NewThemeStarter({
allThemes,

View File

@@ -20,7 +20,7 @@ import { FaShuffle } from 'react-icons/fa6';
import { FiImage, FiSave, FiShuffle, FiX } from 'react-icons/fi';
import { scrollAreaClasses, textClasses, textInputClasses } from 'styles';
import { BackgroundProperties, type Theme } from 'types';
import { ThemeModalContent } from '../ThemeModalContent';
import { ThemeModalContent } from './ThemeModalContent';
export function ThemeEditor({
backgroundList,

View File

@@ -1,7 +1,7 @@
import { Select, Stack, Text } from '@mantine/core';
import { Group, Select, Stack, Text } from '@mantine/core';
import { AppItem } from 'components';
import { selectClasses } from 'styles';
import type { Theme } from 'types';
import { ThemeExampleApp } from './ThemeExampleApp';
export function ThemeModalContent({ selectedTheme }: { selectedTheme: Theme }) {
return (
@@ -73,7 +73,7 @@ export function ThemeModalContent({ selectedTheme }: { selectedTheme: Theme }) {
gap: '.25rem',
backgroundSize: 'cover',
borderBottom: '0px solid',
display: 'grid',
display: 'flex',
gridColumnGap: '5px',
gridRowGap: '5px',
gridTemplateColumns: '1fr 1fr 1fr',
@@ -83,10 +83,46 @@ export function ThemeModalContent({ selectedTheme }: { selectedTheme: Theme }) {
justifyContent: 'center',
alignContent: 'center',
}}
w={'100%'}
>
<ThemeExampleApp selectedTheme={selectedTheme} />
<ThemeExampleApp selectedTheme={selectedTheme} />
<ThemeExampleApp selectedTheme={selectedTheme} />
<Group
style={{
'--mantine-color-primary-0': selectedTheme?.primaryColor,
'--mantine-color-accent-0': selectedTheme?.accentColor,
'--mantine-color-background-0': selectedTheme?.backgroundColor,
'--mantine-color-border-0': selectedTheme?.borderColor,
'--mantine-color-search-0': selectedTheme?.searchColor,
display: 'grid',
gridTemplateColumns: '1fr 1fr 1fr',
}}
w={'100%'}
display={'grid'}
>
<AppItem
url='https://google.com'
icon='FaGoogle'
name='Google'
enabled={1}
scale={1}
linksActive={false}
/>
<AppItem
url='https://google.com'
icon='FaGoogle'
name='Google'
enabled={1}
scale={1}
linksActive={false}
/>
<AppItem
url='https://google.com'
icon='FaGoogle'
name='Google'
enabled={1}
scale={1}
linksActive={false}
/>
</Group>
</Stack>
</Stack>
<Stack gap={0} w={'100%'}>

View File

@@ -0,0 +1 @@
export { ThemeModal } from './ThemeModal';

View File

@@ -2,7 +2,7 @@ import { Group, Overlay, Popover } from '@mantine/core';
import { useLongPress } from '@mantine/hooks';
import { useState } from 'react';
import { ThemeMiniCard } from './ThemeMiniCard';
import { ThemeModalContent } from './ThemeModalContent';
import { ThemeModalContent } from './ThemeModal/components/ThemeModalContent';
export function ThemePreviewMiddleware({
newThemeList,

View File

@@ -20,7 +20,7 @@ import { switchClasses, textClasses } from 'styles';
import type { Theme, ThemePlaylist } from 'types';
import { BackgroundModal } from '../Backgrounds';
import { ThemeCard } from './ThemeCard';
import { ThemeModal } from './ThemeModal/ThemeModal';
import { ThemeModal } from './ThemeModal';
export function Themes() {
const backgroundsEndpoint = 'api/backgrounds';

View File

@@ -1,12 +1,3 @@
export { MiniAppItem } from './MiniAppItem';
export { MiniGroupItem } from './MiniGroupItem';
export { ThemeCard } from './ThemeCard';
export { ThemeExampleApp } from './ThemeExampleApp';
export { ThemeManagementPill } from './ThemeManagementPill';
export { ThemeMiniCard } from './ThemeMiniCard';
export { ThemeModal } from './ThemeModal/ThemeModal';
export { ThemeModalContent } from './ThemeModalContent';
export { ThemePreviewMiddleware } from './ThemePreviewMiddleware';
export { ThemeSelect } from './ThemeSelect';
export { Themes } from './Themes';
export { TimeOfDay } from './TimeOfDay';