Skip to content

Commit

Permalink
feat(Admin): load/save grid layout using LocalStorage API.
Browse files Browse the repository at this point in the history
  • Loading branch information
poirierlouis committed Dec 16, 2024
1 parent d4edf6d commit 0828c62
Show file tree
Hide file tree
Showing 4 changed files with 78 additions and 27 deletions.
20 changes: 20 additions & 0 deletions code/admin/src/AppRepository.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
export class AppRepository {
public static get<T = any>(key: string): T | undefined {
if (!('localStorage' in window)) {
throw new Error('localStorage is not supported on this browser.');
}
const data: string | null = localStorage.getItem(key);

if (!data) {
return;
}
return JSON.parse(data) as T;
}

public static set<T = any>(key: string, value: T): void {
if (!('localStorage' in window)) {
throw new Error('localStorage is not supported on this browser.');
}
localStorage.setItem(key, JSON.stringify(value));
}
}
12 changes: 12 additions & 0 deletions code/admin/src/AppService.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import {AppRepository} from "./AppRepository.ts";
import {Layout} from "react-grid-layout";

export class AppService {
public static loadLayout(): Layout[] {
return AppRepository.get<Layout[]>('dashboard.layout') ?? [];
}

public static saveLayout(layout: Layout[]): void {
AppRepository.set('dashboard.layout', layout);
}
}
2 changes: 1 addition & 1 deletion code/admin/src/Pages/Dashboard/Dashboard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ export default function Dashboard() {
sx={{height: '100%', padding: 0}}>
{isLoading && <CircularProgress/>}

<WidgetGrid widgets={plugins}/>
<WidgetGrid plugins={plugins}/>
</Box>
);
}
71 changes: 45 additions & 26 deletions code/admin/src/Pages/Dashboard/WidgetGrid.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import RGL, {Layout, ReactGridLayoutProps, WidthProvider, WidthProviderProps} from "react-grid-layout";
import {PluginModule, PluginWidget} from "../../WebApi/WebApiData.ts";
import {PluginManifest, PluginModule, PluginWidget} from "../../WebApi/WebApiData.ts";
import {Component, createElement, Suspense, useEffect, useRef, useState} from "react";
import {Alert, Card, CircularProgress} from "@mui/material";
import {ErrorBoundary} from "react-error-boundary";
import {AppService} from "../../AppService.ts";

const GridLayout = WidthProvider(RGL);
type GridLayoutComponent = Component<ReactGridLayoutProps & WidthProviderProps, any, any>;

interface WidgetGridProps {
readonly widgets: PluginModule[];
readonly plugins: PluginModule[];
}

interface WidgetLayout {
Expand All @@ -17,14 +18,26 @@ interface WidgetLayout {
readonly layout: Layout;
}

export default function WidgetGrid({widgets: plugins}: WidgetGridProps) {
export default function WidgetGrid({plugins}: WidgetGridProps) {
const MARGIN: number = 24;
const CELL_SIZE: number = 64;

const root = useRef<GridLayoutComponent | null>(null);
const [columns, setColumns] = useState<number>(28);
const [grid, setGrid] = useState<WidgetLayout[]>([]);

useEffect(() => {
const layouts: Layout[] = AppService.loadLayout();
const grid: WidgetLayout[] = plugins.map((plugin, i) => {
const key: string = getLayoutKey(plugin.manifest);
const layout: Layout | undefined = layouts.find(cache => cache.i === key);

return createLayout(plugin, layout, i);
});

setGrid(grid);
}, [plugins]);

useEffect(() => {
if (!root.current) {
return;
Expand All @@ -37,41 +50,47 @@ export default function WidgetGrid({widgets: plugins}: WidgetGridProps) {
setColumns(Math.ceil(width / CELL_SIZE));
}, [root]);

useEffect(() => {
const layouts: WidgetLayout[] = plugins.map((plugin, i) => {
const widget: PluginWidget = plugin.widget!;
const layout: Layout = {
...widget.layout,
i: `${plugin.manifest.author}/${plugin.manifest.name}`,
x: 0,
y: i,
w: widget.layout.minW ?? 4,
h: widget.layout.minH ?? 4,
};

return {
key: layout.i,
component: createElement(widget.component),
layout: layout
} as WidgetLayout;
});
const getLayoutKey = (manifest: PluginManifest): string => {
return `${manifest.author}/${manifest.name}`;
}

setGrid(layouts);
}, [plugins]);
const createLayout = (plugin: PluginModule, cache: Layout | undefined, i: number): WidgetLayout => {
const widget: PluginWidget = plugin.widget!;
const layout: Layout = {
...widget.layout,
i: getLayoutKey(plugin.manifest),
x: 0,
y: i,
w: widget.layout.minW ?? 4,
h: widget.layout.minH ?? 4,
...cache
};

return {
key: layout.i,
component: createElement(widget.component),
layout: layout
} as WidgetLayout;
}

const handleLayout = (layout: Layout[]) => {
AppService.saveLayout(layout);
};

const layout: Layout[] = grid.map(item => item.layout);

return (
<GridLayout
ref={root}
className="layout"
layout={layout}
isDraggable
isResizable
margin={[MARGIN, MARGIN]}
containerPadding={[0, 0]}
cols={columns}
rowHeight={CELL_SIZE}
layout={layout}
isDraggable
isResizable
onLayoutChange={handleLayout}
>
{grid.map(widget => (
<Card key={widget.key}>
Expand Down

0 comments on commit 0828c62

Please sign in to comment.