fix(dashboard): stabilize four-column widget placement
This commit is contained in:
@@ -26,7 +26,8 @@ import {
|
||||
useUnsavedDraftGuard,
|
||||
type ApiSettings,
|
||||
type AuthInfo,
|
||||
type DashboardWidgetContribution
|
||||
type DashboardWidgetContribution,
|
||||
type DashboardWidgetSize
|
||||
} from "@govoplan/core-webui";
|
||||
import {
|
||||
fetchDashboardLayout,
|
||||
@@ -37,6 +38,9 @@ import WidgetConfigurationDialog from "./WidgetConfigurationDialog";
|
||||
import WidgetLibrary from "./WidgetLibrary";
|
||||
import {
|
||||
appendPlacement,
|
||||
DASHBOARD_COLUMN_COUNT,
|
||||
dashboardWidgetColumnSpan,
|
||||
defaultWidgetSize,
|
||||
defaultDashboardLayout,
|
||||
insertPlacement,
|
||||
layoutFromResponse,
|
||||
@@ -44,6 +48,7 @@ import {
|
||||
layoutsEqual,
|
||||
localLayoutKey,
|
||||
MAX_DASHBOARD_WIDGETS,
|
||||
nextDashboardColumnStart,
|
||||
readLocalLayout,
|
||||
reconcileDashboardLayout,
|
||||
removePlacement,
|
||||
@@ -57,6 +62,7 @@ import type {
|
||||
DashboardDragItem,
|
||||
DashboardDropTarget
|
||||
} from "./dashboardEditorTypes";
|
||||
import { dashboardColumnStartFromPointer } from "./dashboardEditorTypes";
|
||||
|
||||
type LayoutSource = "server" | "browser" | "default";
|
||||
|
||||
@@ -259,8 +265,11 @@ export default function DashboardPage({
|
||||
setDraftLayout((current) => updater(current));
|
||||
}
|
||||
|
||||
function addWidget(widget: DashboardWidgetContribution) {
|
||||
updateDraft((layout) => appendPlacement(layout, widget));
|
||||
function addWidget(
|
||||
widget: DashboardWidgetContribution,
|
||||
columnStart?: number
|
||||
) {
|
||||
updateDraft((layout) => appendPlacement(layout, widget, columnStart));
|
||||
}
|
||||
|
||||
function startDrag(event: ReactDragEvent, item: DashboardDragItem) {
|
||||
@@ -284,20 +293,15 @@ export default function DashboardPage({
|
||||
event.dataTransfer.dropEffect = dragItem.kind === "catalogue" ? "copy" : "move";
|
||||
const bounds = event.currentTarget.getBoundingClientRect();
|
||||
const verticalPosition = (event.clientY - bounds.top) / bounds.height;
|
||||
const edge =
|
||||
verticalPosition < 0.3
|
||||
|| (
|
||||
verticalPosition <= 0.7
|
||||
&& event.clientX < bounds.left + bounds.width / 2
|
||||
)
|
||||
? "before"
|
||||
: "after";
|
||||
const edge = verticalPosition < 0.5 ? "before" : "after";
|
||||
const columnStart = dropColumnStart(event);
|
||||
setDropTarget((current) =>
|
||||
current?.kind === "placement"
|
||||
&& current.instanceId === instanceId
|
||||
&& current.edge === edge
|
||||
&& current.columnStart === columnStart
|
||||
? current
|
||||
: { kind: "placement", instanceId, edge }
|
||||
: { kind: "placement", instanceId, edge, columnStart }
|
||||
);
|
||||
}
|
||||
|
||||
@@ -305,8 +309,54 @@ export default function DashboardPage({
|
||||
if (!dragItem) return;
|
||||
event.preventDefault();
|
||||
event.dataTransfer.dropEffect = dragItem.kind === "catalogue" ? "copy" : "move";
|
||||
const columnStart = dropColumnStart(event);
|
||||
setDropTarget((current) =>
|
||||
current?.kind === "end" ? current : { kind: "end" }
|
||||
current?.kind === "end" && current.columnStart === columnStart
|
||||
? current
|
||||
: { kind: "end", columnStart }
|
||||
);
|
||||
}
|
||||
|
||||
function draggedWidgetSize(): DashboardWidgetSize | null {
|
||||
if (!dragItem) return null;
|
||||
if (dragItem.kind === "placement") {
|
||||
return draftLayout.placements.find(
|
||||
(placement) => placement.instanceId === dragItem.instanceId
|
||||
)?.size ?? null;
|
||||
}
|
||||
const widget = widgetById.get(dragItem.widgetId);
|
||||
return widget ? defaultWidgetSize(widget) : null;
|
||||
}
|
||||
|
||||
function dropColumnStart(
|
||||
event: ReactDragEvent<HTMLElement>
|
||||
): number {
|
||||
const size = draggedWidgetSize();
|
||||
if (!size) return 1;
|
||||
const grid = event.currentTarget.closest<HTMLElement>(
|
||||
"[data-dashboard-grid]"
|
||||
);
|
||||
if (!grid) return 1;
|
||||
const styles = window.getComputedStyle(grid);
|
||||
const visibleColumns = styles.gridTemplateColumns
|
||||
.split(/\s+/)
|
||||
.filter(Boolean).length;
|
||||
if (visibleColumns !== 4) {
|
||||
if (dragItem?.kind === "placement") {
|
||||
return draftLayout.placements.find(
|
||||
(placement) => placement.instanceId === dragItem.instanceId
|
||||
)?.columnStart ?? 1;
|
||||
}
|
||||
return nextDashboardColumnStart(draftLayout.placements, size);
|
||||
}
|
||||
const bounds = grid.getBoundingClientRect();
|
||||
return dashboardColumnStartFromPointer(
|
||||
event.clientX,
|
||||
bounds.left,
|
||||
bounds.width,
|
||||
Number.parseFloat(styles.columnGap),
|
||||
dashboardWidgetColumnSpan(size),
|
||||
DASHBOARD_COLUMN_COUNT
|
||||
);
|
||||
}
|
||||
|
||||
@@ -321,15 +371,28 @@ export default function DashboardPage({
|
||||
&& dropTarget.instanceId === target.instanceId
|
||||
? dropTarget.edge
|
||||
: "after";
|
||||
const columnStart = dropTarget?.columnStart ?? target.columnStart;
|
||||
if (dragItem.kind === "placement") {
|
||||
updateDraft((layout) =>
|
||||
reorderPlacement(layout, dragItem.instanceId, target.instanceId, edge)
|
||||
reorderPlacement(
|
||||
layout,
|
||||
dragItem.instanceId,
|
||||
target.instanceId,
|
||||
edge,
|
||||
columnStart
|
||||
)
|
||||
);
|
||||
} else {
|
||||
const widget = widgetById.get(dragItem.widgetId);
|
||||
if (widget) {
|
||||
updateDraft((layout) =>
|
||||
insertPlacement(layout, widget, target.instanceId, edge)
|
||||
insertPlacement(
|
||||
layout,
|
||||
widget,
|
||||
target.instanceId,
|
||||
edge,
|
||||
columnStart
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -353,16 +416,23 @@ export default function DashboardPage({
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
if (!dragItem) return;
|
||||
const columnStart = dropTarget?.columnStart;
|
||||
if (dragItem.kind === "placement") {
|
||||
const last = draftLayout.placements.at(-1);
|
||||
if (last) {
|
||||
updateDraft((layout) =>
|
||||
reorderPlacement(layout, dragItem.instanceId, last.instanceId, "after")
|
||||
reorderPlacement(
|
||||
layout,
|
||||
dragItem.instanceId,
|
||||
last.instanceId,
|
||||
"after",
|
||||
columnStart
|
||||
)
|
||||
);
|
||||
}
|
||||
} else {
|
||||
const widget = widgetById.get(dragItem.widgetId);
|
||||
if (widget) addWidget(widget);
|
||||
if (widget) addWidget(widget, columnStart);
|
||||
}
|
||||
finishDrag();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user