Reworking Personalization
All checks were successful
Deploy to production / build (push) Successful in 1m4s

This commit is contained in:
Sean Edmunds
2026-02-13 12:51:09 -06:00
parent dd5e3dd4f3
commit e24f1ece32
4 changed files with 151 additions and 96 deletions

32
package-lock.json generated
View File

@@ -8,14 +8,14 @@
"name": "triggerfrontend", "name": "triggerfrontend",
"version": "1.2.0", "version": "1.2.0",
"dependencies": { "dependencies": {
"@gfazioli/mantine-text-animate": "^2.4.0", "@gfazioli/mantine-text-animate": "^2.4.1",
"@hello-pangea/dnd": "^18.0.1", "@hello-pangea/dnd": "^18.0.1",
"@mantine/core": "^8.3.14", "@mantine/core": "^8.3.14",
"@mantine/dates": "^8.3.14", "@mantine/dates": "^8.3.14",
"@mantine/form": "^8.3.14", "@mantine/form": "^8.3.14",
"@mantine/hooks": "^8.3.14", "@mantine/hooks": "^8.3.14",
"@mantine/notifications": "^8.3.14", "@mantine/notifications": "^8.3.14",
"@tanstack/react-query": "^5.90.20", "@tanstack/react-query": "^5.90.21",
"@tanstack/react-query-devtools": "^5.91.3", "@tanstack/react-query-devtools": "^5.91.3",
"@tanstack/react-query-persist-client": "^5.90.22", "@tanstack/react-query-persist-client": "^5.90.22",
"@tanstack/react-table": "^8.21.3", "@tanstack/react-table": "^8.21.3",
@@ -30,7 +30,7 @@
}, },
"devDependencies": { "devDependencies": {
"@types/node": "^25.2.3", "@types/node": "^25.2.3",
"@types/react": "^19.2.13", "@types/react": "^19.2.14",
"@types/react-dom": "^19.2.3", "@types/react-dom": "^19.2.3",
"@vitejs/plugin-react": "^5.1.4", "@vitejs/plugin-react": "^5.1.4",
"@vitest/coverage-v8": "^4.0.18", "@vitest/coverage-v8": "^4.0.18",
@@ -38,7 +38,7 @@
"typescript": "^5.9.3", "typescript": "^5.9.3",
"vite": "^7.3.1", "vite": "^7.3.1",
"vite-plugin-dts": "~4.5.4", "vite-plugin-dts": "~4.5.4",
"vite-tsconfig-paths": "^6.1.0", "vite-tsconfig-paths": "^6.1.1",
"vitest": "^4.0.18" "vitest": "^4.0.18"
} }
}, },
@@ -820,9 +820,9 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/@gfazioli/mantine-text-animate": { "node_modules/@gfazioli/mantine-text-animate": {
"version": "2.4.0", "version": "2.4.1",
"resolved": "https://registry.npmjs.org/@gfazioli/mantine-text-animate/-/mantine-text-animate-2.4.0.tgz", "resolved": "https://registry.npmjs.org/@gfazioli/mantine-text-animate/-/mantine-text-animate-2.4.1.tgz",
"integrity": "sha512-m6zZf6MB8Xm40fEGjSAmcs4gYx0ovWzgIQKhTlsaPDF0w1oD4n1Lj1SX4+vdefSGrGef7Xaazt7mrW2HE2ACdA==", "integrity": "sha512-juAdQpivTXiVPEzk7V8Nwys9d5GyrLbj6X+0qnSvcmPcUvj8fDasHwk2URr2Hv3O71AP3VqOrO1a1otxCoz3hg==",
"peerDependencies": { "peerDependencies": {
"@mantine/core": ">=7.0.0", "@mantine/core": ">=7.0.0",
"@mantine/hooks": ">=7.0.0", "@mantine/hooks": ">=7.0.0",
@@ -1624,9 +1624,9 @@
} }
}, },
"node_modules/@tanstack/react-query": { "node_modules/@tanstack/react-query": {
"version": "5.90.20", "version": "5.90.21",
"resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.90.20.tgz", "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.90.21.tgz",
"integrity": "sha512-vXBxa+qeyveVO7OA0jX1z+DeyCA4JKnThKv411jd5SORpBKgkcVnYKCiBgECvADvniBX7tobwBmg01qq9JmMJw==", "integrity": "sha512-0Lu6y5t+tvlTJMTO7oh5NSpJfpg/5D41LlThfepTixPYkJ0sE2Jj0m0f6yYqujBwIXlId87e234+MxG3D3g7kg==",
"dependencies": { "dependencies": {
"@tanstack/query-core": "5.90.20" "@tanstack/query-core": "5.90.20"
}, },
@@ -1846,9 +1846,9 @@
} }
}, },
"node_modules/@types/react": { "node_modules/@types/react": {
"version": "19.2.13", "version": "19.2.14",
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.13.tgz", "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz",
"integrity": "sha512-KkiJeU6VbYbUOp5ITMIc7kBfqlYkKA5KhEHVrGMmUUMt7NeaZg65ojdPk+FtNrBAOXNVM5QM72jnADjM+XVRAQ==", "integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==",
"devOptional": true, "devOptional": true,
"dependencies": { "dependencies": {
"csstype": "^3.2.2" "csstype": "^3.2.2"
@@ -4189,9 +4189,9 @@
} }
}, },
"node_modules/vite-tsconfig-paths": { "node_modules/vite-tsconfig-paths": {
"version": "6.1.0", "version": "6.1.1",
"resolved": "https://registry.npmjs.org/vite-tsconfig-paths/-/vite-tsconfig-paths-6.1.0.tgz", "resolved": "https://registry.npmjs.org/vite-tsconfig-paths/-/vite-tsconfig-paths-6.1.1.tgz",
"integrity": "sha512-kpd3sY9glHIDaq4V/Tlc1Y8WaKtutoc3B525GHxEVKWX42FKfQsXvjFOemu1I8VIN8pNbrMLWVTbW79JaRUxKg==", "integrity": "sha512-2cihq7zliibCCZ8P9cKJrQBkfgdvcFkOOc3Y02o3GWUDLgqjWsZudaoiuOwO/gzTzy17cS5F7ZPo4bsnS4DGkg==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"debug": "^4.1.1", "debug": "^4.1.1",

View File

@@ -10,14 +10,14 @@
"preview": "vite preview" "preview": "vite preview"
}, },
"dependencies": { "dependencies": {
"@gfazioli/mantine-text-animate": "^2.4.0", "@gfazioli/mantine-text-animate": "^2.4.1",
"@hello-pangea/dnd": "^18.0.1", "@hello-pangea/dnd": "^18.0.1",
"@mantine/core": "^8.3.14", "@mantine/core": "^8.3.14",
"@mantine/dates": "^8.3.14", "@mantine/dates": "^8.3.14",
"@mantine/form": "^8.3.14", "@mantine/form": "^8.3.14",
"@mantine/hooks": "^8.3.14", "@mantine/hooks": "^8.3.14",
"@mantine/notifications": "^8.3.14", "@mantine/notifications": "^8.3.14",
"@tanstack/react-query": "^5.90.20", "@tanstack/react-query": "^5.90.21",
"@tanstack/react-query-devtools": "^5.91.3", "@tanstack/react-query-devtools": "^5.91.3",
"@tanstack/react-query-persist-client": "^5.90.22", "@tanstack/react-query-persist-client": "^5.90.22",
"@tanstack/react-table": "^8.21.3", "@tanstack/react-table": "^8.21.3",
@@ -32,7 +32,7 @@
}, },
"devDependencies": { "devDependencies": {
"@types/node": "^25.2.3", "@types/node": "^25.2.3",
"@types/react": "^19.2.13", "@types/react": "^19.2.14",
"@types/react-dom": "^19.2.3", "@types/react-dom": "^19.2.3",
"@vitejs/plugin-react": "^5.1.4", "@vitejs/plugin-react": "^5.1.4",
"@vitest/coverage-v8": "^4.0.18", "@vitest/coverage-v8": "^4.0.18",
@@ -40,7 +40,7 @@
"typescript": "^5.9.3", "typescript": "^5.9.3",
"vite": "^7.3.1", "vite": "^7.3.1",
"vite-plugin-dts": "~4.5.4", "vite-plugin-dts": "~4.5.4",
"vite-tsconfig-paths": "^6.1.0", "vite-tsconfig-paths": "^6.1.1",
"vitest": "^4.0.18" "vitest": "^4.0.18"
} }
} }

View File

@@ -54,7 +54,25 @@ export default function ExtendedForecastCard({
borderRadius: '8px', borderRadius: '8px',
}} }}
> >
<Text>{item.name}</Text> <Group
justify={item.name.length > 15 ? 'flex-start' : 'center'}
style={{
width: '100%',
whiteSpace: 'nowrap',
overflow: 'hidden',
boxSizing: 'border-box',
position: 'relative',
}}
>
<Text
className={
item.name.length > 15 ? textClasses.autoScrollText : undefined
}
classNames={textClasses}
>
{item.name}
</Text>
</Group>
<WeatherIcon size='md' textDescription={short} /> <WeatherIcon size='md' textDescription={short} />
<Group <Group
justify={short.length > 15 ? 'flex-start' : 'center'} justify={short.length > 15 ? 'flex-start' : 'center'}

View File

@@ -1,8 +1,11 @@
import { import {
Box,
Card, Card,
Grid, Grid,
GridCol, GridCol,
Group,
LoadingOverlay, LoadingOverlay,
Overlay,
Stack, Stack,
Switch, Switch,
Text, Text,
@@ -27,6 +30,7 @@ export function Personalization() {
personal?.enableGuacamole === 1, personal?.enableGuacamole === 1,
); );
const [defaultFont, setDefaultFont] = useState<string>(personal?.defaultFont); const [defaultFont, setDefaultFont] = useState<string>(personal?.defaultFont);
const [customFont, setCustomFont] = useState<boolean>();
const [update, setUpdate] = useState<PersonalData>(); const [update, setUpdate] = useState<PersonalData>();
const [debounced] = useDebouncedValue(update, 1000); const [debounced] = useDebouncedValue(update, 1000);
const { mutateAsync } = useMutationAPI(); const { mutateAsync } = useMutationAPI();
@@ -40,12 +44,12 @@ export function Personalization() {
useEffect(() => { useEffect(() => {
if (Object.keys(personal).length > 0) { if (Object.keys(personal).length > 0) {
setCustomFont(personal.defaultFont !== 'Jost');
setWolEnabled(personal.enableWol === 1); setWolEnabled(personal.enableWol === 1);
setGuacamoleEnabled(personal.enableGuacamole === 1); setGuacamoleEnabled(personal.enableGuacamole === 1);
setDefaultFont(personal.defaultFont); setDefaultFont(personal.defaultFont);
} }
}, [personal]); }, [personal]);
useEffect(() => { useEffect(() => {
if ( if (
Object.keys(personal).length > 0 && Object.keys(personal).length > 0 &&
@@ -155,34 +159,6 @@ export function Personalization() {
</Card> </Card>
</GridCol> </GridCol>
<GridCol span={isMobile ? 12 : 4}> <GridCol span={isMobile ? 12 : 4}>
<Card
className={cardClasses.settingsItem}
classNames={cardClasses}
pt={0}
>
<TextInput
label={'Email'}
className={textInputClasses.settingsItem}
classNames={textInputClasses}
defaultValue={personal?.email}
onChange={(event) => {
const updatedPersonal = {
...personal,
email: event.currentTarget.value,
};
setUpdate(updatedPersonal);
}}
styles={
theme.textShadowsEnabled
? {
label: { boxShadow: '2px 2px var(--mantine-color-border-0)' },
}
: undefined
}
/>
</Card>
</GridCol>
<GridCol span={12}>
<Card <Card
className={cardClasses.settingsItem} className={cardClasses.settingsItem}
classNames={cardClasses} classNames={cardClasses}
@@ -210,7 +186,35 @@ export function Personalization() {
/> />
</Card> </Card>
</GridCol> </GridCol>
<GridCol span={12}> <GridCol span={4}>
<Card
className={cardClasses.settingsItem}
classNames={cardClasses}
pt={0}
>
<TextInput
label={'Email'}
className={textInputClasses.settingsItem}
classNames={textInputClasses}
defaultValue={personal?.email}
onChange={(event) => {
const updatedPersonal = {
...personal,
email: event.currentTarget.value,
};
setUpdate(updatedPersonal);
}}
styles={
theme.textShadowsEnabled
? {
label: { boxShadow: '2px 2px var(--mantine-color-border-0)' },
}
: undefined
}
/>
</Card>
</GridCol>
<GridCol span={8}>
<Card <Card
className={cardClasses.settingsItem} className={cardClasses.settingsItem}
classNames={cardClasses} classNames={cardClasses}
@@ -253,43 +257,77 @@ export function Personalization() {
> >
Extras Extras
</Text> </Text>
<Text>Font Select</Text> <Group justify='center'>
{defaultFont && ( <Switch
<FontPicker label={'Enable Custom Default Font'}
autoLoad className={switchClasses.default}
defaultValue={defaultFont || 'Jost'} classNames={switchClasses}
value={(font: string) => setDefaultFont(font)} checked={customFont}
onChange={(event) => {
setCustomFont(!customFont);
if (!event.target.checked) {
setDefaultFont('Jost');
}
}}
/> />
<Switch
label={'Enable Wake on LAN'}
className={switchClasses.default}
classNames={switchClasses}
checked={wolEnabled}
onChange={(event) => {
setWolEnabled(!wolEnabled);
const updatedPersonal = {
...personal,
enableWol: event.target.checked ? 1 : 0,
};
setUpdate(updatedPersonal);
}}
/>
<Switch
label={'Enable Guacamole Clients'}
className={switchClasses.default}
classNames={switchClasses}
checked={guacamoleEnabled}
onChange={(event) => {
setGuacamoleEnabled(!guacamoleEnabled);
const updatedPersonal = {
...personal,
enableGuacamole: event.target.checked ? 1 : 0,
};
setUpdate(updatedPersonal);
}}
/>
</Group>
{defaultFont && (
<Box pos={'relative'} p={'1rem'}>
{!customFont && (
<Overlay
color='var(--mantine-color-background-0)'
blur={10}
backgroundOpacity={0.75}
style={{ borderRadius: '.5rem' }}
/>
)}
<Text>Default Font</Text>
<FontPicker
autoLoad
defaultValue={defaultFont || 'Jost'}
value={(font: string) => setDefaultFont(font)}
/>
</Box>
)} )}
<Switch <Box pos={'relative'} p={'1rem'} style={{ overflow: 'clip' }}>
label={'Enable Wake on LAN'} {!personal?.enableGuacamole && (
className={switchClasses.default} <Overlay
classNames={switchClasses} color='var(--mantine-color-background-0)'
checked={wolEnabled} zIndex={1}
onChange={(event) => { blur={10}
setWolEnabled(!wolEnabled); backgroundOpacity={0.75}
const updatedPersonal = { style={{ borderRadius: '.5rem' }}
...personal, />
enableWol: event.target.checked ? 1 : 0, )}
};
setUpdate(updatedPersonal);
}}
/>
<Switch
label={'Enable Guacamole Clients'}
className={switchClasses.default}
classNames={switchClasses}
checked={guacamoleEnabled}
onChange={(event) => {
setGuacamoleEnabled(!guacamoleEnabled);
const updatedPersonal = {
...personal,
enableGuacamole: event.target.checked ? 1 : 0,
};
setUpdate(updatedPersonal);
}}
/>
{personal?.enableGuacamole === 1 && (
<TextInput <TextInput
label={'Guacamole Host'} label={'Guacamole Host'}
className={textInputClasses.settingsItem} className={textInputClasses.settingsItem}
@@ -302,17 +340,16 @@ export function Personalization() {
}; };
setUpdate(updatedPersonal); setUpdate(updatedPersonal);
}} }}
styles={ styles={{
theme.textShadowsEnabled label: {
? { zIndex: personal?.enableGuacamole ? 1 : -1,
label: { boxShadow: theme.textShadowsEnabled
boxShadow: '2px 2px var(--mantine-color-border-0)', ? '2px 2px var(--mantine-color-border-0)'
}, : undefined,
} },
: undefined }}
}
/> />
)} </Box>
</Stack> </Stack>
</Card> </Card>
</GridCol> </GridCol>