Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
b317c6b
- refactor: refactor WidgetFrame to MosaicWidget compound components …
BranKein Mar 18, 2026
f3073f9
- feat: add JsonViewerSetting component
BranKein Mar 18, 2026
1d09978
- feat: add WidgetCustomizePage
BranKein Mar 18, 2026
eb21235
- feat: support media widget in Widget Customizer, refactor MediaView…
BranKein Mar 18, 2026
c92ea73
- feat: add store type filtering support and requiredStoreType to wid…
BranKein Mar 18, 2026
ebed4e8
- refactor: consolidate registry utilities into singleton classes and…
BranKein Mar 18, 2026
76c0f81
- refactor: introduce MosaicWidgetContext for widget configuration an…
BranKein Mar 21, 2026
16d9e66
- refactor: migrate widgets to use MosaicWidget compound components a…
BranKein Mar 22, 2026
3d2ee51
- fmt: apply oxfmt, oxlint
BranKein Mar 22, 2026
8428076
- fire: remove redundant comments
BranKein Mar 23, 2026
0867b01
feat: add Segmentation/ObjectDetection media widget scaffolds (deepla…
Junseong0829 Mar 22, 2026
35a9b5a
feat: implement deeplab segmentation overlay in SegmentationMediaWidget
Junseong0829 Mar 22, 2026
e7426da
feat: implement coco-ssd overlay in ObjectDetectionMediaWidget
Junseong0829 Mar 22, 2026
dc67be1
refactor: harden AI media widget stream lifecycle and overlay sync
Junseong0829 Mar 22, 2026
d17de97
chore(frontend): apply oxfmt to satisfy ci fmt check
Junseong0829 Mar 22, 2026
c78106d
feat: add Object/Segment Setting.tsx file & migrate WidgetDescriptor …
Junseong0829 Mar 23, 2026
6c580f3
fix: keep overlay labels readable on flip in AI media widgets
Junseong0829 Mar 23, 2026
73461bf
Merge pull request #54 from ACSL-MOSAIC/feature/42-jslee
Junseong0829 Mar 23, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
447 changes: 441 additions & 6 deletions frontend/package-lock.json

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@
"@tanstack/react-query": "^5.84.1",
"@tanstack/react-query-devtools": "^5.84.1",
"@tanstack/react-router": "1.19.1",
"@tensorflow-models/coco-ssd": "^2.2.2",
"@tensorflow-models/deeplab": "^0.2.2",
"@tensorflow/tfjs": "^3.21.0",
"@types/leaflet": "^1.9.20",
"@types/react-grid-layout": "^1.3.5",
"@types/uuid": "^10.0.0",
Expand Down
3 changes: 2 additions & 1 deletion frontend/src/components/Common/SidebarItems.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,15 @@ import { Link as RouterLink } from "@tanstack/react-router";
import { useState } from "react";
import { FaBars, FaRobot } from "react-icons/fa";
import { FaLink } from "react-icons/fa";
import { FiMap, FiSettings, FiUsers } from "react-icons/fi";
import { FiCode, FiMap, FiSettings, FiUsers } from "react-icons/fi";

import useAuth from "@/hooks/useAuth.ts";

const items = [
{ icon: FaRobot, title: "Robots", path: "/robots" },
{ icon: FiMap, title: "Occupancy Maps", path: "/occupancy-maps" },
{ icon: FaLink, title: "Dashboard", path: "/dashboard" },
{ icon: FiCode, title: "Widget Test", path: "/widget-customize" },
{ icon: FiSettings, title: "User Settings", path: "/settings" },
];

Expand Down
91 changes: 60 additions & 31 deletions frontend/src/components/Dashboard/DashboardGrid.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,15 @@ import { type Layout, Responsive, WidthProvider } from "react-grid-layout";

import { getTabConfigApi, updateTabConfigApi } from "@/client/service/dashboard.api.ts";
import RobotConnectionPanel from "@/components/Dashboard/RobotConnectionPanel.tsx";
import { WidgetFactory } from "@/components/Dashboard/WidgetFactory.tsx";
import { WidgetFactory } from "@/components/Dashboard/Widgets/WidgetFactory.tsx";
import useAuth from "@/hooks/useAuth.ts";
import "react-grid-layout/css/styles.css";
import "react-resizable/css/styles.css";
import { useMosaicWebRTCConnection } from "@/hooks/useMosaicWebRTCConnection.ts";
import { useRobotInfo } from "@/hooks/useRobotInfo.ts";
import { RobotConnector, type TabConfig, type WidgetConfig } from "@/mosaic";
import { DASHBOARD_STORAGE_KEYS } from "@/utils";
import { getWidgetDescriptor } from "@/widgets/_utils/widgetRegistry.ts";

const ResponsiveGridLayout = WidthProvider(Responsive);

Expand Down Expand Up @@ -83,29 +84,6 @@ export default function DashboardGrid({ tabId }: DashboardGridProps) {
const editableWidgetsRef = useRef<WidgetConfig[]>([]);
const isLayoutDirtyRef = useRef(false);

const { data: tabConfig, isPending: isConfigLoading } = useQuery({
queryKey: ["parsedDashboardTabConfig", tabId],
queryFn: async () => {
const tabConfigDto = await getTabConfigApi(tabId);
const widgets = JSON.parse(tabConfigDto.widgets).widgets as WidgetConfig[];
return {
id: tabConfigDto.id,
name: tabConfigDto.name,
widgets: widgets.map((widget) => {
return {
id: widget.id,
type: widget.type,
position: widget.position,
connectors: widget.connectors.map((connector) => {
return new RobotConnector(connector.robotId, connector.connectorId);
}),
};
}),
} as TabConfig;
},
enabled: !!user,
});

const saveLayoutMutation = useMutation({
mutationFn: async ({
targetTabId,
Expand Down Expand Up @@ -142,6 +120,58 @@ export default function DashboardGrid({ tabId }: DashboardGridProps) {
},
});

const onUpdateWidgetParams = (widgetId: string, params?: any) => {
if (!tabConfig) {
return;
}

const newWidgets = editableWidgetsRef.current.map((widget) => {
if (widget.id !== widgetId) {
return widget;
}

return {
...widget,
params: params,
};
});

saveLayoutMutation.mutate({
targetTabId: tabConfig.id,
widgets: newWidgets,
});
};

const { data: tabConfig, isPending: isConfigLoading } = useQuery({
queryKey: ["parsedDashboardTabConfig", tabId],
queryFn: async () => {
const tabConfigDto = await getTabConfigApi(tabId);
const widgets = JSON.parse(tabConfigDto.widgets).widgets as WidgetConfig[];
return {
id: tabConfigDto.id,
name: tabConfigDto.name,
widgets: widgets.map((widget) => {
return {
id: widget.id,
type: widget.type,
position: widget.position,
connectors: widget.connectors.map((connector) => {
return new RobotConnector(connector.robotId, connector.connectorId);
}),
params: {
...getWidgetDescriptor(widget.type)?.getDefaultParams(),
...widget.params,
},
onUpdateWidgetParams: (params?: any) => {
onUpdateWidgetParams(widget.id, params);
},
};
}),
} as TabConfig;
},
enabled: !!user,
});

useEffect(() => {
if (!tabConfig) {
setEditableWidgets([]);
Expand Down Expand Up @@ -343,22 +373,21 @@ export default function DashboardGrid({ tabId }: DashboardGridProps) {
onLayoutChange={handleLayoutChange}
onDragStop={(layout) => handleLayoutCommit(layout)}
onResizeStop={(layout) => handleLayoutCommit(layout)}
isDraggable={true}
isResizable={true}
margin={[16, 16]}
isDraggable
isResizable
draggableHandle=".draggable-header"
>
{editableWidgets.map((widgetConfig) => (
<Box
key={widgetConfig.id}
bg="white"
p={4}
borderRadius="md"
boxShadow="sm"
p={3}
paddingTop={1}
borderRadius="sm"
boxShadow="xs"
height="100%"
display="flex"
flexDirection="column"
mb={4}
>
<WidgetFactory widgetConfig={widgetConfig} />
</Box>
Expand Down
36 changes: 0 additions & 36 deletions frontend/src/components/Dashboard/WidgetFactory.tsx

This file was deleted.

42 changes: 0 additions & 42 deletions frontend/src/components/Dashboard/WidgetFooter.tsx

This file was deleted.

38 changes: 0 additions & 38 deletions frontend/src/components/Dashboard/WidgetFrame.tsx

This file was deleted.

68 changes: 0 additions & 68 deletions frontend/src/components/Dashboard/WidgetHeader.tsx

This file was deleted.

46 changes: 46 additions & 0 deletions frontend/src/components/Dashboard/Widgets/ErrorWidget.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { Box, Code, Flex, HStack, Icon, Text, VStack } from "@chakra-ui/react";
import { LuTriangleAlert } from "react-icons/lu";

export interface ErrorWidgetProps {
error: string;
}

export function ErrorWidget({ error }: ErrorWidgetProps) {
return (
<Flex h="100%" align="center" justify="center">
<Box w="100%" h="100%" bgGradient="linear(to-b, red.50, white)" p={4}>
<VStack align="start" gap={4} h="100%">
<HStack gap={2}>
<Flex
w="7"
h="7"
align="center"
justify="center"
borderRadius="full"
bg="red.100"
color="red.700"
>
<Icon as={LuTriangleAlert} boxSize={4} />
</Flex>
<VStack align="start" gap={0}>
<Text fontSize="sm" fontWeight="semibold" color="red.800">
Widget Error
</Text>
<Text fontSize="xs" color="gray.600">
This widget could not be loaded due to an error.
</Text>
</VStack>
</HStack>
<Box w="100%" p={3} border="1px solid" borderColor="red.200" borderRadius="md" bg="white">
<Text fontSize="xs" color="gray.500" mb={1}>
Error
</Text>
<Code fontSize="xs" color="red.700">
{error}
</Code>
</Box>
</VStack>
</Box>
</Flex>
);
}
Loading
Loading