Skip to content

Commit 9fd6feb

Browse files
committed
allow separate layouts for mobile and desktop
1 parent 54d08e0 commit 9fd6feb

File tree

12 files changed

+92
-41
lines changed

12 files changed

+92
-41
lines changed

backend/src/config/config.json

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,6 @@
1-
{}
1+
{
2+
"layout": {
3+
"desktop": [],
4+
"mobile": []
5+
}
6+
}

backend/src/routes/layout.route.ts

Lines changed: 24 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { Request, Response, Router } from 'express';
2+
import fsSync from 'fs';
23
import fs from 'fs/promises';
34
import StatusCodes from 'http-status-codes';
45
import path from 'path';
@@ -7,12 +8,20 @@ export const layoutRoute = Router();
78

89
const CONFIG_FILE = path.join(__dirname, '../config/config.json');
910

11+
const loadConfig = () => {
12+
if (fsSync.existsSync(CONFIG_FILE)) {
13+
return JSON.parse(fsSync.readFileSync(CONFIG_FILE, 'utf-8'));
14+
}
15+
return { layout: { desktop: [], mobile: [] } };
16+
};
17+
1018
// GET - Retrieve the saved layout JSON from disk
1119
layoutRoute.get('/', async (_req: Request, res: Response): Promise<void> => {
1220
try {
13-
const data = await fs.readFile(CONFIG_FILE, 'utf-8');
14-
const layout = JSON.parse(data);
15-
res.status(StatusCodes.OK).json(layout.layout);
21+
const config = loadConfig();
22+
console.log('loading layout', config);
23+
24+
res.status(StatusCodes.OK).json(config.layout);
1625
} catch (error) {
1726
res.status(StatusCodes.INTERNAL_SERVER_ERROR).json({
1827
message: 'Error reading layout file',
@@ -24,14 +33,18 @@ layoutRoute.get('/', async (_req: Request, res: Response): Promise<void> => {
2433
// POST - Save the incoming JSON layout to disk
2534
layoutRoute.post('/', async (req: Request, res: Response): Promise<void> => {
2635
try {
27-
const layout = req.body;
28-
29-
if (!layout || typeof layout !== 'object') {
30-
res.status(StatusCodes.BAD_REQUEST).json({ message: 'Invalid layout data' });
31-
return;
32-
}
33-
// TODO: write only layout section
34-
await fs.writeFile(CONFIG_FILE, JSON.stringify({ layout: layout }, null, 2), 'utf-8');
36+
console.log('saving layout body', req.body);
37+
38+
const { desktop, mobile } = req.body;
39+
const config = loadConfig();
40+
41+
console.log('config', config);
42+
config.layout.desktop = desktop && desktop.length > 0 ? desktop : config.layout.desktop;
43+
config.layout.mobile = mobile && mobile.length > 0 ? mobile : desktop;
44+
console.log('config2', config);
45+
46+
47+
await fs.writeFile(CONFIG_FILE, JSON.stringify(config, null, 2), 'utf-8');
3548
res.status(StatusCodes.OK).json({ message: 'Layout saved successfully' });
3649
} catch (error) {
3750
res.status(StatusCodes.INTERNAL_SERVER_ERROR).json({

backend/tsconfig.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
},
1313
"include": [
1414
"index.ts",
15-
"./src/config/config.json"
15+
"./src/config/config.json",
16+
"../shared"
1617
]
1718
}

frontend/src/api/dash-api.ts

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
import axios from 'axios';
22
import { StatusCodes } from 'http-status-codes';
33

4+
import { DashboardLayout } from '../../../shared/types/config';
5+
import { DashboardItem } from '../../../shared/types/dashboard-item';
46
import { BACKEND_URL } from '../constants/constants';
5-
import { DashboardItem, Icon } from '../types';
7+
import { Icon } from '../types';
68

79

810
export class DashApi {
@@ -18,16 +20,18 @@ export class DashApi {
1820
return res.data;
1921
}
2022

21-
public static async getLayout(): Promise<DashboardItem[]> {
23+
public static async getLayout(): Promise<DashboardLayout> {
2224
const res = await axios.get(`${BACKEND_URL}/api/layout`);
2325

2426
return res.data;
2527
}
2628

27-
public static async saveLayout(layout: DashboardItem[]): Promise<void> {
28-
const res = await axios.post(`${BACKEND_URL}/api/layout`, layout);
29-
30-
return res.data;
29+
public static async saveLayout(layout: { desktop: DashboardItem[], mobile: DashboardItem[] }): Promise<void> {
30+
try {
31+
await axios.post(`${BACKEND_URL}/api/layout`, layout);
32+
} catch (error) {
33+
console.error('Failed to save layout:', error);
34+
}
3135
}
3236

3337
public static async getSystemInformation(): Promise<any> {

frontend/src/components/dnd/DashboardGrid.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,9 @@ import { SortableAppShortcut } from './SortableAppShortcut';
1919
import { SortableDateTimeWidget } from './SortableDateTime';
2020
import { SortableSystemMonitorWidget } from './SortableSystemMonitor';
2121
import { SortableWeatherWidget } from './SortableWeather';
22+
import { DashboardItem } from '../../../../shared/types/dashboard-item';
2223
import { useAppContext } from '../../context/useAppContext';
23-
import { DashboardItem, ITEM_TYPE } from '../../types';
24+
import { ITEM_TYPE } from '../../types';
2425
import { AddEditForm } from '../forms/AddEditForm';
2526
import { CenteredModal } from '../modals/CenteredModal';
2627
import { ConfirmationOptions, PopupManager } from '../modals/PopupManager';

frontend/src/components/forms/AddEditForm.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,13 @@ import { useEffect, useState } from 'react';
33
import { useForm } from 'react-hook-form';
44
import { CheckboxElement, FormContainer, SelectElement, TextFieldElement } from 'react-hook-form-mui';
55

6+
import { DashboardItem } from '../../../../shared/types/dashboard-item';
67
import { DashApi } from '../../api/dash-api';
78
import { useAppContext } from '../../context/useAppContext';
89
import { useIsMobile } from '../../hooks/useIsMobile';
910
import { COLORS, styles } from '../../theme/styles';
1011
import { theme } from '../../theme/theme';
11-
import { DashboardItem, Icon, ITEM_TYPE, NewItem } from '../../types';
12+
import { Icon, ITEM_TYPE, NewItem } from '../../types';
1213
import { IconSearch } from '../IconSearch';
1314

1415
type Props = {

frontend/src/context/AppContext.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { createContext, Dispatch, SetStateAction } from 'react';
22

3-
import { DashboardItem, NewItem } from '../types';
3+
import { DashboardItem } from '../../../shared/types/dashboard-item';
4+
import { NewItem } from '../types';
45

56
export interface IAppContext {
67
dashboardLayout: DashboardItem[];

frontend/src/context/AppContextProvider.tsx

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,46 @@
1+
import { useMediaQuery } from '@mui/material';
12
import { ReactNode, useState } from 'react';
23
import shortid from 'shortid';
34

45
import { AppContext } from './AppContext';
6+
import { DashboardLayout } from '../../../shared/types/config';
7+
import { DashboardItem } from '../../../shared/types/dashboard-item';
58
import { DashApi } from '../api/dash-api';
69
import { initialItems } from '../constants/constants';
7-
import { DashboardItem, NewItem } from '../types';
10+
import { theme } from '../theme/theme';
11+
import { NewItem } from '../types';
812

913
type Props = {
1014
children: ReactNode
1115
};
1216

1317
export const AppContextProvider = ({ children }: Props) => {
1418
const [dashboardLayout, setDashboardLayout] = useState<DashboardItem[]>(initialItems);
19+
const isMobile = useMediaQuery(theme.breakpoints.down('sm'));
1520

1621
const getSavedLayout = async () => {
17-
console.log('getSavedLayout');
22+
console.log('Fetching saved layout');
1823

19-
const res = await DashApi.getLayout();
24+
const res = await DashApi.getLayout(); // Retrieves { desktop: [], mobile: [] }
2025

21-
if (res && res.length > 0) {
22-
setDashboardLayout(res);
23-
return res;
26+
if (res) {
27+
const selectedLayout = isMobile ? res.mobile : res.desktop;
28+
setDashboardLayout(selectedLayout);
29+
return selectedLayout;
2430
}
2531
return [];
2632
};
2733

2834
const saveLayout = async (items: DashboardItem[]) => {
29-
const res = await DashApi.saveLayout(items);
30-
console.log(res);
35+
console.log('saving layout');
36+
37+
const res = await DashApi.getLayout();
38+
const updatedLayout: DashboardLayout = isMobile
39+
? { ...res, mobile: items }
40+
: { ...res, desktop: items };
41+
42+
console.log('Saving updated layout:', updatedLayout);
43+
await DashApi.saveLayout(updatedLayout);
3144
};
3245

3346
const refreshDashboard = async () => {
@@ -40,7 +53,6 @@ export const AppContextProvider = ({ children }: Props) => {
4053
}
4154
};
4255

43-
4456
const addItem = (itemToAdd: NewItem) => {
4557
console.log('add item');
4658

frontend/src/types/index.ts

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,6 @@ export enum ITEM_TYPE {
88

99
}
1010

11-
export type DashboardItem = {
12-
id: string;
13-
label: string;
14-
type: string;
15-
url?: string;
16-
icon?: { path: string; name: string, source?: string };
17-
showName?: boolean;
18-
}
19-
2011
export type NewItem = {
2112
name?: string;
2213
icon?: { path: string; name: string };

frontend/tsconfig.app.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
"noUncheckedSideEffectImports": true,
2626
},
2727
"include": [
28-
"./src"
28+
"./src",
29+
"../shared"
2930
]
3031
}

0 commit comments

Comments
 (0)