{laneElements.map((element) => {
- const item = element.type === "item" ? preview.items.find((item) => item.id === element.id) : undefined;
- const WidgetIcon = item?.kind ? widgetCatalogIcons[item.kind] : undefined;
+ const nestedItems =
+ element.type === "section"
+ ? elements.filter((candidate) => candidate.type === "item" && candidate.sectionId === element.id)
+ : [];
return (
- {item?.iconUrl ? (
-
- ) : WidgetIcon ? (
-
- ) : null}
+ {element.type === "section" ? (
+
+ {nestedItems.map((nestedItem) => (
+
+ {renderItemIcon(nestedItem.id)}
+
+ ))}
+
+ ) : (
+ renderItemIcon(element.id)
+ )}
);
})}
diff --git a/packages/api/src/router/board.ts b/packages/api/src/router/board.ts
index 2095dc608..21cdb9ef4 100644
--- a/packages/api/src/router/board.ts
+++ b/packages/api/src/router/board.ts
@@ -283,7 +283,7 @@ export const boardRouter = createTRPCRouter({
const previewLayoutIds = dbBoards.flatMap((board) => board.layouts.map((layout) => layout.id));
const rootSectionIds = dbBoards.flatMap((board) => board.sections.map((section) => section.id));
- const [previewItemLayouts, previewSectionLayouts] =
+ const [rootPreviewItemLayouts, previewSectionLayouts] =
previewLayoutIds.length > 0 && rootSectionIds.length > 0
? await Promise.all([
ctx.db.query.itemLayouts.findMany({
@@ -339,6 +339,43 @@ export const boardRouter = createTRPCRouter({
])
: [[], []];
+ const previewContainerSectionIds = [
+ ...new Set(
+ previewSectionLayouts
+ .filter((layout) => layout.section.kind === "container")
+ .map((layout) => layout.sectionId),
+ ),
+ ];
+ const nestedPreviewItemLayouts =
+ previewLayoutIds.length > 0 && previewContainerSectionIds.length > 0
+ ? await ctx.db.query.itemLayouts.findMany({
+ columns: {
+ itemId: true,
+ layoutId: true,
+ sectionId: true,
+ xOffset: true,
+ yOffset: true,
+ width: true,
+ height: true,
+ },
+ with: {
+ item: {
+ columns: { kind: true, options: true },
+ },
+ },
+ where: and(
+ inArray(itemLayouts.layoutId, previewLayoutIds),
+ inArray(itemLayouts.sectionId, previewContainerSectionIds),
+ gte(itemLayouts.xOffset, 0),
+ gte(itemLayouts.yOffset, 0),
+ lt(itemLayouts.yOffset, maxManageOverviewPreviewRows),
+ gt(itemLayouts.width, 0),
+ gt(itemLayouts.height, 0),
+ ),
+ })
+ : [];
+ const previewItemLayouts = [...rootPreviewItemLayouts, ...nestedPreviewItemLayouts];
+
const appIdByItemId = new Map(
previewItemLayouts.flatMap((layout) => {
if (layout.item.kind !== "app") return [];
@@ -389,19 +426,6 @@ export const boardRouter = createTRPCRouter({
return columnCount !== undefined && columnCount > 0 && layout.xOffset < columnCount;
};
- const itemPreview = previewLayout
- ? previewItemLayouts
- .filter((layout) => layout.layoutId === previewLayout.id && isInsideRootLane(layout))
- .map((layout) => {
- const appId = appIdByItemId.get(layout.itemId);
- return {
- id: layout.itemId,
- kind: layout.item.kind,
- iconUrl: appId ? appIconUrlById.get(appId) : undefined,
- layouts: [layout],
- };
- })
- : [];
const containerPreview = previewLayout
? previewSectionLayouts
.filter(
@@ -417,6 +441,34 @@ export const boardRouter = createTRPCRouter({
layouts: [layout],
}))
: [];
+ const visibleContainerSizeById = new Map(
+ containerPreview.map((section) => [section.id, section.layouts[0]] as const),
+ );
+ const isInsideVisibleContainer = (layout: { sectionId: string; xOffset: number; yOffset: number }) => {
+ const containerLayout = visibleContainerSizeById.get(layout.sectionId);
+ return (
+ containerLayout !== undefined &&
+ layout.xOffset < containerLayout.width &&
+ layout.yOffset < containerLayout.height
+ );
+ };
+ const itemPreview = previewLayout
+ ? previewItemLayouts
+ .filter(
+ (layout) =>
+ layout.layoutId === previewLayout.id &&
+ (isInsideRootLane(layout) || isInsideVisibleContainer(layout)),
+ )
+ .map((layout) => {
+ const appId = appIdByItemId.get(layout.itemId);
+ return {
+ id: layout.itemId,
+ kind: layout.item.kind,
+ iconUrl: appId ? appIconUrlById.get(appId) : undefined,
+ layouts: [layout],
+ };
+ })
+ : [];
return {
...board,
diff --git a/packages/api/src/router/test/board.spec.ts b/packages/api/src/router/test/board.spec.ts
index 382b5f88a..d62eb9f31 100644
--- a/packages/api/src/router/test/board.spec.ts
+++ b/packages/api/src/router/test/board.spec.ts
@@ -350,6 +350,7 @@ describe("getManageOverview", () => {
const mobileItemId = createId();
const offscreenItemId = createId();
const nestedItemId = createId();
+ const nestedAppId = createId();
await db.insert(users).values({ id: defaultCreatorId });
await db.insert(boards).values([
@@ -400,11 +401,16 @@ describe("getManageOverview", () => {
width: 6,
height: 3,
});
+ await db.insert(apps).values({
+ id: nestedAppId,
+ name: "Nested app",
+ iconUrl: "https://example.com/nested.svg",
+ });
await db.insert(items).values([
{ id: itemId, boardId, kind: "clock" },
{ id: mobileItemId, boardId, kind: "clock" },
{ id: offscreenItemId, boardId, kind: "clock" },
- { id: nestedItemId, boardId, kind: "clock" },
+ { id: nestedItemId, boardId, kind: "app", options: SuperJSON.stringify({ appId: nestedAppId }) },
]);
await db.insert(itemLayouts).values([
{
@@ -461,6 +467,12 @@ describe("getManageOverview", () => {
kind: "clock",
layouts: [{ layoutId: baseLayoutId, sectionId: rootSectionId, width: 4, height: 2 }],
},
+ {
+ id: nestedItemId,
+ kind: "app",
+ iconUrl: "https://example.com/nested.svg",
+ layouts: [{ layoutId: baseLayoutId, sectionId: containerSectionId, width: 2, height: 1 }],
+ },
],
sections: [
{ id: rootSectionId, kind: "empty" },
@@ -473,7 +485,7 @@ describe("getManageOverview", () => {
},
});
expect(result[0]?.preview?.layouts).toHaveLength(1);
- expect(result[0]?.preview?.items.map((item) => item.id)).toEqual([itemId]);
+ expect(result[0]?.preview?.items.map((item) => item.id)).toEqual([itemId, nestedItemId]);
expect(result[0]?.preview?.sections).toHaveLength(2);
});