@agent-native/core 0.80.0 → 0.80.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/corpus/README.md +2 -2
- package/corpus/core/CHANGELOG.md +28 -0
- package/corpus/core/docs/content/locales/ar-SA/skills-guide.mdx +5 -0
- package/corpus/core/docs/content/locales/ar-SA/template-design.mdx +14 -1
- package/corpus/core/docs/content/locales/de-DE/skills-guide.mdx +5 -0
- package/corpus/core/docs/content/locales/de-DE/template-design.mdx +15 -1
- package/corpus/core/docs/content/locales/es-ES/skills-guide.mdx +5 -0
- package/corpus/core/docs/content/locales/es-ES/template-design.mdx +15 -1
- package/corpus/core/docs/content/locales/fr-FR/skills-guide.mdx +5 -0
- package/corpus/core/docs/content/locales/fr-FR/template-design.mdx +15 -1
- package/corpus/core/docs/content/locales/hi-IN/skills-guide.mdx +5 -0
- package/corpus/core/docs/content/locales/hi-IN/template-design.mdx +15 -1
- package/corpus/core/docs/content/locales/ja-JP/skills-guide.mdx +5 -0
- package/corpus/core/docs/content/locales/ja-JP/template-design.mdx +16 -1
- package/corpus/core/docs/content/locales/ko-KR/skills-guide.mdx +5 -0
- package/corpus/core/docs/content/locales/ko-KR/template-design.mdx +14 -1
- package/corpus/core/docs/content/locales/pt-BR/skills-guide.mdx +5 -0
- package/corpus/core/docs/content/locales/pt-BR/template-design.mdx +14 -1
- package/corpus/core/docs/content/locales/zh-CN/skills-guide.mdx +5 -0
- package/corpus/core/docs/content/locales/zh-CN/template-design.mdx +14 -1
- package/corpus/core/docs/content/locales/zh-TW/skills-guide.mdx +5 -0
- package/corpus/core/docs/content/locales/zh-TW/template-design.mdx +14 -1
- package/corpus/core/docs/content/skills-guide.mdx +5 -0
- package/corpus/core/docs/content/template-design.mdx +14 -1
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/application-state/handlers.ts +1 -1
- package/corpus/core/src/brand-kit/index.ts +1 -1
- package/corpus/core/src/cli/skills.ts +18 -0
- package/corpus/core/src/client/AgentPanel.tsx +1 -1
- package/corpus/core/src/client/MultiTabAssistantChat.tsx +1 -1
- package/corpus/core/src/client/extensions/portable-extension.ts +1 -1
- package/corpus/core/src/code-agents/transcript-normalizer.ts +1 -1
- package/corpus/core/src/extensions/path.ts +1 -4
- package/corpus/core/src/integrations/a2a-continuation-processor.ts +1 -1
- package/corpus/core/src/mcp/build-server.ts +2 -2
- package/corpus/core/src/mcp/embed-app.ts +35 -1
- package/corpus/core/src/org/handlers.ts +5 -5
- package/corpus/core/src/org/plugin.ts +2 -2
- package/corpus/core/src/scripts/call-agent.ts +1 -1
- package/corpus/core/src/scripts/db/safety.ts +1 -1
- package/corpus/core/src/server/auth.ts +3 -0
- package/corpus/core/src/server/builder-design-systems.ts +308 -0
- package/corpus/core/src/server/core-routes-plugin.ts +29 -3
- package/corpus/core/src/server/index.ts +8 -0
- package/corpus/core/src/styles/agent-native.css +14 -7
- package/corpus/core/src/usage/store.ts +2 -2
- package/corpus/templates/analytics/actions/dashboard-mutation-api.ts +298 -15
- package/corpus/templates/analytics/actions/get-sql-dashboard.ts +9 -4
- package/corpus/templates/analytics/actions/mutate-dashboard.ts +18 -2
- package/corpus/templates/analytics/actions/query-agent-native-analytics.ts +12 -2
- package/corpus/templates/analytics/app/hooks/use-replay-storage-status.ts +66 -0
- package/corpus/templates/analytics/app/i18n/zh-TW.ts +37 -0
- package/corpus/templates/analytics/app/i18n-data.ts +404 -0
- package/corpus/templates/analytics/app/pages/Settings.tsx +333 -0
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/PanelEditorDialog.tsx +2 -1
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/dashboard-layout.ts +121 -3
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/index.tsx +49 -26
- package/corpus/templates/analytics/app/pages/sessions/SessionsPage.tsx +137 -36
- package/corpus/templates/analytics/changelog/2026-06-29-analytics-agents-can-now-place-new-charts-into-requested-das.md +6 -0
- package/corpus/templates/analytics/changelog/2026-06-29-analytics-queries-now-report-failed-source-query-errors-inst.md +6 -0
- package/corpus/templates/analytics/changelog/2026-06-29-charts-can-now-be-dropped-between-full-dashboard-rows-to-cre.md +6 -0
- package/corpus/templates/analytics/changelog/2026-06-29-dashboard-email-reports-include-the-full-dashboard-image-wit.md +6 -0
- package/corpus/templates/analytics/changelog/2026-06-29-main-surface-border-reaches-the-top.md +6 -0
- package/corpus/templates/analytics/changelog/2026-06-29-session-replay-now-records-playable-sessions-in-production-a.md +6 -0
- package/corpus/templates/analytics/changelog/2026-06-29-set-up-session-replay-storage-from-settings-connect-builder-.md +6 -0
- package/corpus/templates/analytics/scripts/cleanup-empty-session-recordings.ts +102 -0
- package/corpus/templates/analytics/server/lib/dashboard-report.ts +49 -26
- package/corpus/templates/analytics/server/lib/real-data-actions.ts +81 -20
- package/corpus/templates/analytics/server/lib/replay-storage.ts +79 -0
- package/corpus/templates/analytics/server/lib/s3-upload-provider.ts +255 -0
- package/corpus/templates/analytics/server/lib/session-replay.ts +29 -8
- package/corpus/templates/analytics/server/plugins/agent-chat.ts +11 -2
- package/corpus/templates/analytics/server/plugins/onboarding.ts +97 -0
- package/corpus/templates/clips/desktop/src/lib/recorder.ts +24 -0
- package/corpus/templates/clips/desktop/src/lib/transcription-capture.ts +105 -5
- package/corpus/templates/content/actions/set-image-alt-text.ts +1 -1
- package/corpus/templates/content/shared/document-export.ts +1 -1
- package/corpus/templates/design/.agents/skills/design-systems/SKILL.md +8 -18
- package/corpus/templates/design/.agents/skills/visual-edit/SKILL.md +17 -0
- package/corpus/templates/design/actions/add-localhost-screens.ts +32 -5
- package/corpus/templates/design/actions/index-design-system-with-builder.ts +259 -0
- package/corpus/templates/design/actions/update-file.ts +9 -2
- package/corpus/templates/design/app/components/design/CanvasContextMenu.tsx +33 -75
- package/corpus/templates/design/app/components/design/DesignCanvas.tsx +72 -9
- package/corpus/templates/design/app/components/design/DesignExtensionsPanel.tsx +41 -39
- package/corpus/templates/design/app/components/design/EditPanel.tsx +1029 -249
- package/corpus/templates/design/app/components/design/LayersPanel.tsx +450 -101
- package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +978 -154
- package/corpus/templates/design/app/components/design/TweaksPanel.tsx +7 -9
- package/corpus/templates/design/app/components/design/inspector/AlignmentMatrix.tsx +133 -66
- package/corpus/templates/design/app/components/design/inspector/AutoLayoutMatrix.tsx +1234 -344
- package/corpus/templates/design/app/components/design/inspector/ConstraintsWidget.tsx +144 -94
- package/corpus/templates/design/app/components/design/inspector/ExportSettingsPanel.tsx +301 -72
- package/corpus/templates/design/app/components/design/inspector/FigmaColorPicker.tsx +876 -286
- package/corpus/templates/design/app/components/design/inspector/GradientEditor.tsx +601 -0
- package/corpus/templates/design/app/components/design/inspector/ImageFillControls.tsx +242 -0
- package/corpus/templates/design/app/components/design/inspector/ScrubInput.tsx +30 -7
- package/corpus/templates/design/app/components/design/inspector/ShaderControls.tsx +212 -155
- package/corpus/templates/design/app/components/design/inspector/ShaderFillsPanel.tsx +423 -0
- package/corpus/templates/design/app/components/design/inspector/figma-icons.tsx +487 -107
- package/corpus/templates/design/app/components/design/inspector/index.ts +27 -0
- package/corpus/templates/design/app/global.css +29 -3
- package/corpus/templates/design/app/i18n/zh-TW.ts +13 -0
- package/corpus/templates/design/app/i18n-data.ts +239 -1
- package/corpus/templates/design/app/pages/DesignEditor.tsx +1023 -353
- package/corpus/templates/design/app/pages/DesignSystemSetup.tsx +57 -221
- package/corpus/templates/design/changelog/2026-06-29-canvas-selection-no-longer-flashes-broken-prototype-html-whe.md +6 -0
- package/corpus/templates/design/changelog/2026-06-29-command-menu-selections-are-clearer-in-dark-mode.md +6 -0
- package/corpus/templates/design/changelog/2026-06-29-design-mode-top-chrome-now-groups-collaborators-preview-shar.md +6 -0
- package/corpus/templates/design/changelog/2026-06-29-element-selection-now-stays-on-the-intended-layer-after-sele.md +6 -0
- package/corpus/templates/design/changelog/2026-06-29-screen-overview-full-view-controls-now-sit-above-frames-inst.md +6 -0
- package/corpus/templates/design/changelog/2026-06-29-screen-overview-tools-can-draw-shapes-create-screens-and-kee.md +6 -0
- package/corpus/templates/design/changelog/2026-06-29-the-pen-tool-now-creates-anchor-based-bezier-vectors-with-ha.md +6 -0
- package/corpus/templates/design/server/handlers/import-figma-system.ts +38 -85
- package/corpus/templates/design/server/lib/design-snapshot.ts +9 -1
- package/corpus/templates/design/shared/code-layer.ts +209 -9
- package/corpus/templates/design/shared/html-content.ts +21 -0
- package/corpus/templates/design/shared/pen-path.ts +207 -0
- package/corpus/templates/mail/app/lib/utils.ts +2 -2
- package/corpus/templates/slides/.agents/skills/design-systems/SKILL.md +9 -10
- package/corpus/templates/slides/AGENTS.md +3 -2
- package/corpus/templates/slides/actions/import-file.ts +26 -17
- package/corpus/templates/slides/actions/index-design-system-with-builder.ts +259 -0
- package/corpus/templates/slides/app/components/design-system/DesignSystemSetup.tsx +35 -167
- package/corpus/templates/slides/app/components/design-system/fig-import-response.ts +9 -13
- package/corpus/templates/slides/app/context/DeckContext.tsx +1 -1
- package/corpus/templates/slides/app/i18n/ar-SA.ts +9 -37
- package/corpus/templates/slides/app/i18n/de-DE.ts +10 -25
- package/corpus/templates/slides/app/i18n/en-US.ts +9 -26
- package/corpus/templates/slides/app/i18n/es-ES.ts +10 -28
- package/corpus/templates/slides/app/i18n/fr-FR.ts +10 -28
- package/corpus/templates/slides/app/i18n/hi-IN.ts +9 -26
- package/corpus/templates/slides/app/i18n/ja-JP.ts +10 -22
- package/corpus/templates/slides/app/i18n/ko-KR.ts +9 -23
- package/corpus/templates/slides/app/i18n/pt-BR.ts +10 -28
- package/corpus/templates/slides/app/i18n/zh-CN.ts +9 -22
- package/corpus/templates/slides/app/i18n/zh-TW.ts +9 -22
- package/corpus/templates/slides/server/handlers/import-figma-system.ts +44 -12
- package/corpus/templates/slides/server/lib/chat-attachments.ts +1 -1
- package/corpus/templates/videos/app/components/NewCompositionPopover.tsx +1 -1
- package/dist/application-state/handlers.js +1 -1
- package/dist/application-state/handlers.js.map +1 -1
- package/dist/brand-kit/index.d.ts +1 -1
- package/dist/brand-kit/index.js +1 -1
- package/dist/brand-kit/index.js.map +1 -1
- package/dist/cli/skills.d.ts.map +1 -1
- package/dist/cli/skills.js +18 -0
- package/dist/cli/skills.js.map +1 -1
- package/dist/client/AgentPanel.js +1 -1
- package/dist/client/AgentPanel.js.map +1 -1
- package/dist/client/MultiTabAssistantChat.js +1 -1
- package/dist/client/MultiTabAssistantChat.js.map +1 -1
- package/dist/client/extensions/portable-extension.d.ts +1 -1
- package/dist/client/extensions/portable-extension.d.ts.map +1 -1
- package/dist/client/extensions/portable-extension.js.map +1 -1
- package/dist/code-agents/transcript-normalizer.js +1 -1
- package/dist/code-agents/transcript-normalizer.js.map +1 -1
- package/dist/collab/awareness.d.ts +2 -2
- package/dist/collab/awareness.d.ts.map +1 -1
- package/dist/collab/routes.d.ts +1 -1
- package/dist/extensions/path.d.ts +1 -1
- package/dist/extensions/path.d.ts.map +1 -1
- package/dist/extensions/path.js.map +1 -1
- package/dist/file-upload/actions/upload-image.d.ts +2 -2
- package/dist/integrations/a2a-continuation-processor.js +1 -1
- package/dist/integrations/a2a-continuation-processor.js.map +1 -1
- package/dist/mcp/build-server.d.ts +1 -1
- package/dist/mcp/build-server.d.ts.map +1 -1
- package/dist/mcp/build-server.js +1 -1
- package/dist/mcp/build-server.js.map +1 -1
- package/dist/mcp/embed-app.d.ts.map +1 -1
- package/dist/mcp/embed-app.js +35 -1
- package/dist/mcp/embed-app.js.map +1 -1
- package/dist/notifications/routes.d.ts +3 -3
- package/dist/observability/routes.d.ts +8 -8
- package/dist/org/handlers.js +5 -5
- package/dist/org/handlers.js.map +1 -1
- package/dist/org/plugin.js +2 -2
- package/dist/org/plugin.js.map +1 -1
- package/dist/resources/handlers.d.ts +1 -1
- package/dist/scripts/call-agent.js +1 -1
- package/dist/scripts/call-agent.js.map +1 -1
- package/dist/scripts/db/safety.js +1 -1
- package/dist/scripts/db/safety.js.map +1 -1
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/dist/server/auth.d.ts.map +1 -1
- package/dist/server/auth.js +3 -0
- package/dist/server/auth.js.map +1 -1
- package/dist/server/builder-design-systems.d.ts +28 -0
- package/dist/server/builder-design-systems.d.ts.map +1 -0
- package/dist/server/builder-design-systems.js +217 -0
- package/dist/server/builder-design-systems.js.map +1 -0
- package/dist/server/core-routes-plugin.d.ts +1 -0
- package/dist/server/core-routes-plugin.d.ts.map +1 -1
- package/dist/server/core-routes-plugin.js +20 -3
- package/dist/server/core-routes-plugin.js.map +1 -1
- package/dist/server/index.d.ts +1 -0
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +1 -0
- package/dist/server/index.js.map +1 -1
- package/dist/styles/agent-native.css +14 -7
- package/dist/usage/store.js +2 -2
- package/dist/usage/store.js.map +1 -1
- package/docs/content/locales/ar-SA/skills-guide.mdx +5 -0
- package/docs/content/locales/ar-SA/template-design.mdx +14 -1
- package/docs/content/locales/de-DE/skills-guide.mdx +5 -0
- package/docs/content/locales/de-DE/template-design.mdx +15 -1
- package/docs/content/locales/es-ES/skills-guide.mdx +5 -0
- package/docs/content/locales/es-ES/template-design.mdx +15 -1
- package/docs/content/locales/fr-FR/skills-guide.mdx +5 -0
- package/docs/content/locales/fr-FR/template-design.mdx +15 -1
- package/docs/content/locales/hi-IN/skills-guide.mdx +5 -0
- package/docs/content/locales/hi-IN/template-design.mdx +15 -1
- package/docs/content/locales/ja-JP/skills-guide.mdx +5 -0
- package/docs/content/locales/ja-JP/template-design.mdx +16 -1
- package/docs/content/locales/ko-KR/skills-guide.mdx +5 -0
- package/docs/content/locales/ko-KR/template-design.mdx +14 -1
- package/docs/content/locales/pt-BR/skills-guide.mdx +5 -0
- package/docs/content/locales/pt-BR/template-design.mdx +14 -1
- package/docs/content/locales/zh-CN/skills-guide.mdx +5 -0
- package/docs/content/locales/zh-CN/template-design.mdx +14 -1
- package/docs/content/locales/zh-TW/skills-guide.mdx +5 -0
- package/docs/content/locales/zh-TW/template-design.mdx +14 -1
- package/docs/content/skills-guide.mdx +5 -0
- package/docs/content/template-design.mdx +14 -1
- package/package.json +1 -1
- package/corpus/templates/design/actions/import-figma-file.ts +0 -166
- package/corpus/templates/slides/server/lib/fig-design-system.ts +0 -110
|
@@ -1,14 +1,29 @@
|
|
|
1
1
|
import {
|
|
2
|
+
agentNativePath,
|
|
2
3
|
ChangelogSettingsCard,
|
|
3
4
|
LanguagePicker,
|
|
4
5
|
SettingsTabsPage,
|
|
5
6
|
openAgentSettings,
|
|
7
|
+
useBuilderConnectFlow,
|
|
8
|
+
useBuilderStatus,
|
|
6
9
|
useT,
|
|
7
10
|
} from "@agent-native/core/client";
|
|
8
11
|
import { TeamPage } from "@agent-native/core/client/org";
|
|
12
|
+
import {
|
|
13
|
+
IconCheck,
|
|
14
|
+
IconChevronDown,
|
|
15
|
+
IconCloud,
|
|
16
|
+
IconExternalLink,
|
|
17
|
+
IconKey,
|
|
18
|
+
IconLoader2,
|
|
19
|
+
IconServer,
|
|
20
|
+
} from "@tabler/icons-react";
|
|
21
|
+
import { useState } from "react";
|
|
9
22
|
import { Link } from "react-router";
|
|
23
|
+
import { toast } from "sonner";
|
|
10
24
|
|
|
11
25
|
import { useAuth } from "@/components/auth/AuthProvider";
|
|
26
|
+
import { Badge } from "@/components/ui/badge";
|
|
12
27
|
import { Button } from "@/components/ui/button";
|
|
13
28
|
import {
|
|
14
29
|
Card,
|
|
@@ -17,14 +32,160 @@ import {
|
|
|
17
32
|
CardHeader,
|
|
18
33
|
CardTitle,
|
|
19
34
|
} from "@/components/ui/card";
|
|
35
|
+
import {
|
|
36
|
+
Collapsible,
|
|
37
|
+
CollapsibleContent,
|
|
38
|
+
CollapsibleTrigger,
|
|
39
|
+
} from "@/components/ui/collapsible";
|
|
40
|
+
import { Input } from "@/components/ui/input";
|
|
20
41
|
import { Label } from "@/components/ui/label";
|
|
42
|
+
import { useReplayStorageStatus } from "@/hooks/use-replay-storage-status";
|
|
43
|
+
import { cn } from "@/lib/utils";
|
|
21
44
|
|
|
22
45
|
import changelog from "../../CHANGELOG.md?raw";
|
|
23
46
|
|
|
47
|
+
const S3_STORAGE_FIELDS = [
|
|
48
|
+
{
|
|
49
|
+
key: "S3_ENDPOINT",
|
|
50
|
+
labelKey: "settings.s3EndpointLabel",
|
|
51
|
+
placeholder: "https://s3.us-east-1.amazonaws.com",
|
|
52
|
+
required: true,
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
key: "S3_BUCKET",
|
|
56
|
+
labelKey: "settings.s3BucketLabel",
|
|
57
|
+
placeholder: "my-replays-bucket",
|
|
58
|
+
required: true,
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
key: "S3_ACCESS_KEY_ID",
|
|
62
|
+
labelKey: "settings.s3AccessKeyLabel",
|
|
63
|
+
placeholder: "AKIA...",
|
|
64
|
+
required: true,
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
key: "S3_SECRET_ACCESS_KEY",
|
|
68
|
+
labelKey: "settings.s3SecretAccessKeyLabel",
|
|
69
|
+
placeholder: "••••••••",
|
|
70
|
+
required: true,
|
|
71
|
+
secret: true,
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
key: "S3_REGION",
|
|
75
|
+
labelKey: "settings.s3RegionLabel",
|
|
76
|
+
placeholder: "us-east-1",
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
key: "S3_PUBLIC_BASE_URL",
|
|
80
|
+
labelKey: "settings.s3PublicBaseUrlLabel",
|
|
81
|
+
placeholder: "https://cdn.example.com",
|
|
82
|
+
},
|
|
83
|
+
] as const;
|
|
84
|
+
|
|
85
|
+
async function saveS3StorageSettings(
|
|
86
|
+
values: Record<string, string>,
|
|
87
|
+
): Promise<void> {
|
|
88
|
+
const vars = S3_STORAGE_FIELDS.map((field) => ({
|
|
89
|
+
key: field.key,
|
|
90
|
+
value: (values[field.key] ?? "").trim(),
|
|
91
|
+
})).filter((entry) => entry.value.length > 0);
|
|
92
|
+
|
|
93
|
+
if (vars.length === 0) {
|
|
94
|
+
throw new Error("Enter at least one storage value.");
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
for (const { key, value } of vars) {
|
|
98
|
+
const res = await fetch(agentNativePath("/_agent-native/secrets/adhoc"), {
|
|
99
|
+
method: "POST",
|
|
100
|
+
headers: { "Content-Type": "application/json" },
|
|
101
|
+
body: JSON.stringify({
|
|
102
|
+
name: key,
|
|
103
|
+
value,
|
|
104
|
+
scope: "workspace",
|
|
105
|
+
description: "Analytics S3-compatible replay storage", // i18n-ignore -- secret metadata description, not visible UI
|
|
106
|
+
}),
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
if (!res.ok) {
|
|
110
|
+
const body = (await res.json().catch(() => null)) as {
|
|
111
|
+
error?: string;
|
|
112
|
+
} | null;
|
|
113
|
+
throw new Error(body?.error ?? `Save failed (${res.status})`);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
24
118
|
export default function Settings() {
|
|
25
119
|
const { auth } = useAuth();
|
|
26
120
|
const t = useT();
|
|
27
121
|
|
|
122
|
+
const storageStatus = useReplayStorageStatus();
|
|
123
|
+
const builderStatus = useBuilderStatus();
|
|
124
|
+
const builderConnect = useBuilderConnectFlow({
|
|
125
|
+
popupUrl:
|
|
126
|
+
builderStatus.status?.cliAuthUrl ?? builderStatus.status?.connectUrl,
|
|
127
|
+
trackingSource: "analytics_settings",
|
|
128
|
+
trackingFlow: "replay_storage",
|
|
129
|
+
onConnected: async () => {
|
|
130
|
+
await Promise.all([storageStatus.refetch(), builderStatus.refetch()]);
|
|
131
|
+
toast.success(t("settings.builderConnectedToast"));
|
|
132
|
+
},
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
const [savingStorage, setSavingStorage] = useState(false);
|
|
136
|
+
const [s3Values, setS3Values] = useState<Record<string, string>>({});
|
|
137
|
+
const [s3Expanded, setS3Expanded] = useState(false);
|
|
138
|
+
|
|
139
|
+
const storageConfigured = !!storageStatus.data?.configured;
|
|
140
|
+
const activeProviderName = storageStatus.data?.activeProvider?.name ?? null;
|
|
141
|
+
const activeProviderId = storageStatus.data?.activeProvider?.id ?? null;
|
|
142
|
+
const builderConnected = Boolean(
|
|
143
|
+
builderConnect.configured ||
|
|
144
|
+
builderStatus.status?.configured ||
|
|
145
|
+
storageStatus.data?.builderConfigured ||
|
|
146
|
+
activeProviderId === "builder",
|
|
147
|
+
);
|
|
148
|
+
const builderOrgName =
|
|
149
|
+
builderConnect.orgName ?? builderStatus.status?.orgName ?? null;
|
|
150
|
+
const builderStatusLoading =
|
|
151
|
+
storageStatus.isLoading ||
|
|
152
|
+
builderStatus.loading ||
|
|
153
|
+
!builderConnect.hasFetchedStatus;
|
|
154
|
+
const s3Configured = activeProviderId === "s3";
|
|
155
|
+
const s3Collapsed = builderConnected && !s3Expanded;
|
|
156
|
+
|
|
157
|
+
async function handleSaveS3Storage() {
|
|
158
|
+
const missing = s3Configured
|
|
159
|
+
? []
|
|
160
|
+
: S3_STORAGE_FIELDS.filter(
|
|
161
|
+
(field) =>
|
|
162
|
+
"required" in field &&
|
|
163
|
+
field.required &&
|
|
164
|
+
!(s3Values[field.key] ?? "").trim(),
|
|
165
|
+
);
|
|
166
|
+
if (missing.length > 0) {
|
|
167
|
+
toast.error(t("settings.storageRequired"));
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
setSavingStorage(true);
|
|
172
|
+
try {
|
|
173
|
+
await saveS3StorageSettings(s3Values);
|
|
174
|
+
setS3Values((current) => ({
|
|
175
|
+
...current,
|
|
176
|
+
S3_SECRET_ACCESS_KEY: "",
|
|
177
|
+
}));
|
|
178
|
+
await storageStatus.refetch();
|
|
179
|
+
toast.success(t("settings.storageSaved"));
|
|
180
|
+
} catch (err) {
|
|
181
|
+
toast.error(
|
|
182
|
+
err instanceof Error ? err.message : t("settings.storageSaveFailed"),
|
|
183
|
+
);
|
|
184
|
+
} finally {
|
|
185
|
+
setSavingStorage(false);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
28
189
|
return (
|
|
29
190
|
<SettingsTabsPage
|
|
30
191
|
teamLabel={t("navigation.team")}
|
|
@@ -67,6 +228,178 @@ export default function Settings() {
|
|
|
67
228
|
</CardContent>
|
|
68
229
|
</Card>
|
|
69
230
|
|
|
231
|
+
<Card
|
|
232
|
+
id="replay-storage"
|
|
233
|
+
className="bg-card border-border/50 scroll-mt-16"
|
|
234
|
+
>
|
|
235
|
+
<CardHeader>
|
|
236
|
+
<CardTitle className="text-base flex items-center gap-2">
|
|
237
|
+
<IconCloud className="size-4 text-primary" />
|
|
238
|
+
{t("settings.replayStorage")}
|
|
239
|
+
</CardTitle>
|
|
240
|
+
<CardDescription>
|
|
241
|
+
{t("settings.replayStorageDescription")}
|
|
242
|
+
</CardDescription>
|
|
243
|
+
</CardHeader>
|
|
244
|
+
<CardContent className="space-y-4">
|
|
245
|
+
<div
|
|
246
|
+
className={cn(
|
|
247
|
+
"flex flex-col gap-3 rounded-md border px-3 py-3 sm:flex-row sm:items-center sm:justify-between",
|
|
248
|
+
builderConnected
|
|
249
|
+
? "border-primary/35 bg-primary/5"
|
|
250
|
+
: "border-border bg-accent/30",
|
|
251
|
+
)}
|
|
252
|
+
>
|
|
253
|
+
<div className="min-w-0">
|
|
254
|
+
<div className="flex items-center gap-2 text-sm font-medium">
|
|
255
|
+
{builderConnected ? (
|
|
256
|
+
<IconCheck className="h-4 w-4 text-primary" />
|
|
257
|
+
) : (
|
|
258
|
+
<IconKey className="h-4 w-4 text-muted-foreground" />
|
|
259
|
+
)}
|
|
260
|
+
{builderStatusLoading
|
|
261
|
+
? t("settings.checkingBuilder")
|
|
262
|
+
: builderConnected
|
|
263
|
+
? t("settings.builderConnected")
|
|
264
|
+
: t("settings.connectBuilder")}
|
|
265
|
+
</div>
|
|
266
|
+
<p className="mt-0.5 text-xs text-muted-foreground">
|
|
267
|
+
{builderConnected
|
|
268
|
+
? builderOrgName
|
|
269
|
+
? t("settings.builderConnectedFor", {
|
|
270
|
+
orgName: builderOrgName,
|
|
271
|
+
})
|
|
272
|
+
: t("settings.builderConnectedGeneric")
|
|
273
|
+
: t("settings.builderIncludes")}
|
|
274
|
+
</p>
|
|
275
|
+
</div>
|
|
276
|
+
{builderConnected ? (
|
|
277
|
+
<Badge variant="secondary" className="shrink-0">
|
|
278
|
+
{t("settings.connected")}
|
|
279
|
+
</Badge>
|
|
280
|
+
) : (
|
|
281
|
+
<Button
|
|
282
|
+
type="button"
|
|
283
|
+
variant="outline"
|
|
284
|
+
size="sm"
|
|
285
|
+
className="shrink-0"
|
|
286
|
+
onClick={() =>
|
|
287
|
+
builderConnect.start({
|
|
288
|
+
trackingSource: "analytics_settings_replay_storage",
|
|
289
|
+
trackingFlow: "replay_storage",
|
|
290
|
+
})
|
|
291
|
+
}
|
|
292
|
+
disabled={builderConnect.connecting || builderStatusLoading}
|
|
293
|
+
>
|
|
294
|
+
{builderConnect.connecting ? (
|
|
295
|
+
<IconLoader2 className="h-4 w-4 animate-spin" />
|
|
296
|
+
) : (
|
|
297
|
+
<IconExternalLink className="h-4 w-4" />
|
|
298
|
+
)}
|
|
299
|
+
{t("settings.connectBuilder")}
|
|
300
|
+
</Button>
|
|
301
|
+
)}
|
|
302
|
+
</div>
|
|
303
|
+
|
|
304
|
+
<Collapsible
|
|
305
|
+
open={builderConnected ? !s3Collapsed : true}
|
|
306
|
+
onOpenChange={(open) => setS3Expanded(open)}
|
|
307
|
+
>
|
|
308
|
+
<div className="rounded-md border border-border">
|
|
309
|
+
<div className="flex flex-col gap-3 px-3 py-3 sm:flex-row sm:items-center sm:justify-between">
|
|
310
|
+
<div className="min-w-0">
|
|
311
|
+
<div className="flex flex-wrap items-center gap-2 text-sm font-medium">
|
|
312
|
+
<IconServer className="h-4 w-4 text-muted-foreground" />
|
|
313
|
+
{t("settings.s3Title")}
|
|
314
|
+
<Badge variant="outline" className="text-[10px]">
|
|
315
|
+
{t("settings.secondary")}
|
|
316
|
+
</Badge>
|
|
317
|
+
{s3Configured ? (
|
|
318
|
+
<Badge variant="secondary" className="text-[10px]">
|
|
319
|
+
{t("settings.active")}
|
|
320
|
+
</Badge>
|
|
321
|
+
) : null}
|
|
322
|
+
</div>
|
|
323
|
+
<p className="mt-0.5 text-xs text-muted-foreground">
|
|
324
|
+
{builderConnected
|
|
325
|
+
? t("settings.s3BuilderConnectedDescription")
|
|
326
|
+
: storageConfigured && activeProviderName
|
|
327
|
+
? t("settings.s3CurrentProvider", {
|
|
328
|
+
providerName: activeProviderName,
|
|
329
|
+
})
|
|
330
|
+
: t("settings.s3OwnBucketDescription")}
|
|
331
|
+
</p>
|
|
332
|
+
</div>
|
|
333
|
+
{builderConnected ? (
|
|
334
|
+
<CollapsibleTrigger asChild>
|
|
335
|
+
<Button
|
|
336
|
+
type="button"
|
|
337
|
+
variant="ghost"
|
|
338
|
+
size="sm"
|
|
339
|
+
className="shrink-0"
|
|
340
|
+
>
|
|
341
|
+
{s3Collapsed
|
|
342
|
+
? t("settings.configureS3")
|
|
343
|
+
: t("settings.hideS3")}
|
|
344
|
+
<IconChevronDown
|
|
345
|
+
className={cn(
|
|
346
|
+
"h-4 w-4 transition-transform",
|
|
347
|
+
!s3Collapsed && "rotate-180",
|
|
348
|
+
)}
|
|
349
|
+
/>
|
|
350
|
+
</Button>
|
|
351
|
+
</CollapsibleTrigger>
|
|
352
|
+
) : null}
|
|
353
|
+
</div>
|
|
354
|
+
|
|
355
|
+
<CollapsibleContent>
|
|
356
|
+
<div className="space-y-4 border-t border-border px-3 py-4">
|
|
357
|
+
<div className="grid gap-4 sm:grid-cols-2">
|
|
358
|
+
{S3_STORAGE_FIELDS.map((field) => (
|
|
359
|
+
<div key={field.key} className="space-y-1.5">
|
|
360
|
+
<Label htmlFor={field.key}>
|
|
361
|
+
{t(field.labelKey)}
|
|
362
|
+
</Label>
|
|
363
|
+
<Input
|
|
364
|
+
id={field.key}
|
|
365
|
+
type={
|
|
366
|
+
"secret" in field && field.secret
|
|
367
|
+
? "password"
|
|
368
|
+
: "text"
|
|
369
|
+
}
|
|
370
|
+
value={s3Values[field.key] ?? ""}
|
|
371
|
+
onChange={(event) =>
|
|
372
|
+
setS3Values((current) => ({
|
|
373
|
+
...current,
|
|
374
|
+
[field.key]: event.target.value,
|
|
375
|
+
}))
|
|
376
|
+
}
|
|
377
|
+
placeholder={field.placeholder}
|
|
378
|
+
autoComplete="off"
|
|
379
|
+
disabled={savingStorage}
|
|
380
|
+
/>
|
|
381
|
+
</div>
|
|
382
|
+
))}
|
|
383
|
+
</div>
|
|
384
|
+
|
|
385
|
+
<div className="flex justify-end">
|
|
386
|
+
<Button
|
|
387
|
+
onClick={handleSaveS3Storage}
|
|
388
|
+
disabled={savingStorage || storageStatus.isLoading}
|
|
389
|
+
>
|
|
390
|
+
{savingStorage && (
|
|
391
|
+
<IconLoader2 className="h-4 w-4 animate-spin" />
|
|
392
|
+
)}
|
|
393
|
+
{t("settings.saveStorage")}
|
|
394
|
+
</Button>
|
|
395
|
+
</div>
|
|
396
|
+
</div>
|
|
397
|
+
</CollapsibleContent>
|
|
398
|
+
</div>
|
|
399
|
+
</Collapsible>
|
|
400
|
+
</CardContent>
|
|
401
|
+
</Card>
|
|
402
|
+
|
|
70
403
|
<Card className="bg-card border-border/50">
|
|
71
404
|
<CardHeader>
|
|
72
405
|
<CardTitle className="text-base">
|
|
@@ -229,7 +229,7 @@ function PanelEditorContent({
|
|
|
229
229
|
`The \`demo\` source is reserved for the built-in Node Exporter demo and does not satisfy REAL_DATA_REQUIRED unless the user explicitly asks to work on that demo dashboard. ` +
|
|
230
230
|
`If no source can answer, report the exact unavailable/error result instead of saving a panel with guessed schema or metrics. ` +
|
|
231
231
|
`Use the \`mutate-dashboard\` action with code like \`dashboard.insertPanel({"id":"new-panel","title":"New Panel","source":"first-party","chartType":"metric","width":1,"sql":"SELECT COUNT(*) AS value FROM analytics_events"}).atBottom();\` ` +
|
|
232
|
-
`to append, or \`.before("panel-id")\`, \`.after("panel-id")\`, or \`.atIndex(n)\` to place the panel. ` +
|
|
232
|
+
`to append, or \`.nextTo("panel-id")\`, \`.atRow(2)\`, \`.atRowStart(2)\`, \`.before("panel-id")\`, \`.after("panel-id")\`, or \`.atIndex(n)\` to place the panel. Prefer \`.nextTo("panel-id")\` or \`.atRow(rowNumber)\` for visible row placement requests; they keep the chart in the intended rendered row and expand/rebalance that row when needed. ` +
|
|
233
233
|
`Panel shape: { id (unique slug), title, sql, source ('bigquery'|'ga4'|'amplitude'|'first-party'|'demo'|'prometheus'), chartType ('line'|'area'|'bar'|'metric'|'table'|'pie'|'section'), width (legacy integer 1..6; set to 1 unless editing existing data), tab? (use 'Group / Tab' for grouped tabs), columns? (section panels only - 1..6 max panels per row for panels following this section), config? }. ` +
|
|
234
234
|
`Visible layout auto-fits by row: one panel in a row spans the row, two split it, three split it into thirds, up to the section column limit. ` +
|
|
235
235
|
`For amplitude panels, sql is a JSON descriptor: {"event":"event name","groupBy":"property","days":30}. ` +
|
|
@@ -238,6 +238,7 @@ function PanelEditorContent({
|
|
|
238
238
|
`For prometheus panels, sql is a JSON descriptor: {"promql":"rate(http_requests_total[5m])","mode":"range","range":"1h","step":"30s"}. mode defaults to "range"; range defaults to "1h"; step is auto if omitted. Returned rows have shape {timestamp, series, value} — set config.xKey="timestamp", config.yKey="value", and a single series in config.yKeys for clean charting. ` +
|
|
239
239
|
`Config is optional: { xKey, yKey, yKeys, yFormatter ('number'|'currency'|'percent'), description, columns, pivot, limit, color, colors, stacked, legend, valueLabels }. ` +
|
|
240
240
|
`Chart legends render automatically; set config.legend=false only when the user explicitly asks to hide the legend. ` +
|
|
241
|
+
`Use \`get-sql-dashboard.layout.groups[].rows[].rowNumber/panelIds\` to identify and verify visible rows. ` +
|
|
241
242
|
`Consult the data dictionary first via \`list-data-dictionary --search <topic>\`, then use AGENTS.md, .agents/skills, and connected data-source instructions before writing SQL. ` +
|
|
242
243
|
`Every BigQuery panel is dry-run validated on save — if columns/tables are wrong the save returns a 400 with the BQ error and you must fix the SQL and retry. ` +
|
|
243
244
|
`After the mutation saves, verify the returned panelCount, appliedOps, and insertedPanelIds; the UI refreshes automatically.`,
|
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
clampDashboardColumns,
|
|
3
|
+
clampPanelWidth,
|
|
4
|
+
MAX_DASHBOARD_COLUMNS,
|
|
5
|
+
type SqlPanel,
|
|
6
|
+
} from "./types";
|
|
2
7
|
|
|
3
8
|
export type DashboardPanelRow = {
|
|
4
9
|
key: string;
|
|
@@ -38,6 +43,19 @@ export type DashboardClientRect = {
|
|
|
38
43
|
bottom: number;
|
|
39
44
|
};
|
|
40
45
|
|
|
46
|
+
export type DashboardDropSlotCandidate = {
|
|
47
|
+
id: string;
|
|
48
|
+
slot: DashboardDropSlot;
|
|
49
|
+
rect: DashboardClientRect;
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
export type DashboardColumnExpansion = {
|
|
53
|
+
columns: number;
|
|
54
|
+
sectionPanelId: string | null;
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
const COLUMN_SLOT_EDGE_PROXIMITY_PX = 48;
|
|
58
|
+
|
|
41
59
|
function rowKey(panels: SqlPanel[], index: number): string {
|
|
42
60
|
return panels.map((panel) => panel.id).join(":") || `empty-${index}`;
|
|
43
61
|
}
|
|
@@ -62,6 +80,77 @@ export function distanceFromPointerToRect(
|
|
|
62
80
|
return Math.hypot(dx, dy);
|
|
63
81
|
}
|
|
64
82
|
|
|
83
|
+
function pointerIsInsideRect(
|
|
84
|
+
pointer: DashboardPointerCoordinates,
|
|
85
|
+
rect: DashboardClientRect,
|
|
86
|
+
): boolean {
|
|
87
|
+
return (
|
|
88
|
+
pointer.x >= rect.left &&
|
|
89
|
+
pointer.x <= rect.right &&
|
|
90
|
+
pointer.y >= rect.top &&
|
|
91
|
+
pointer.y <= rect.bottom
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function pointerIsHorizontallyInsideRect(
|
|
96
|
+
pointer: DashboardPointerCoordinates,
|
|
97
|
+
rect: DashboardClientRect,
|
|
98
|
+
): boolean {
|
|
99
|
+
return pointer.x >= rect.left && pointer.x <= rect.right;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function closestDropSlotCandidate(
|
|
103
|
+
pointer: DashboardPointerCoordinates,
|
|
104
|
+
candidates: DashboardDropSlotCandidate[],
|
|
105
|
+
): { candidate: DashboardDropSlotCandidate; distance: number } | null {
|
|
106
|
+
let closest: DashboardDropSlotCandidate | null = null;
|
|
107
|
+
let closestDistance = Number.MAX_VALUE;
|
|
108
|
+
|
|
109
|
+
for (const candidate of candidates) {
|
|
110
|
+
const distance = distanceFromPointerToRect(pointer, candidate.rect);
|
|
111
|
+
if (distance < closestDistance) {
|
|
112
|
+
closest = candidate;
|
|
113
|
+
closestDistance = distance;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return closest ? { candidate: closest, distance: closestDistance } : null;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export function preferredDropSlotId(
|
|
121
|
+
pointer: DashboardPointerCoordinates,
|
|
122
|
+
candidates: DashboardDropSlotCandidate[],
|
|
123
|
+
): string | null {
|
|
124
|
+
if (candidates.length === 0) return null;
|
|
125
|
+
|
|
126
|
+
const columnSlotsUnderPointerX = candidates.filter(
|
|
127
|
+
(candidate) =>
|
|
128
|
+
candidate.slot.type === "column" &&
|
|
129
|
+
pointerIsHorizontallyInsideRect(pointer, candidate.rect),
|
|
130
|
+
);
|
|
131
|
+
const closestColumnSlot = closestDropSlotCandidate(
|
|
132
|
+
pointer,
|
|
133
|
+
columnSlotsUnderPointerX,
|
|
134
|
+
);
|
|
135
|
+
if (
|
|
136
|
+
closestColumnSlot &&
|
|
137
|
+
closestColumnSlot.distance <= COLUMN_SLOT_EDGE_PROXIMITY_PX
|
|
138
|
+
) {
|
|
139
|
+
return closestColumnSlot.candidate.id;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const containingSlots = candidates.filter((candidate) =>
|
|
143
|
+
pointerIsInsideRect(pointer, candidate.rect),
|
|
144
|
+
);
|
|
145
|
+
const closestContainingSlot = closestDropSlotCandidate(
|
|
146
|
+
pointer,
|
|
147
|
+
containingSlots,
|
|
148
|
+
);
|
|
149
|
+
if (closestContainingSlot) return closestContainingSlot.candidate.id;
|
|
150
|
+
|
|
151
|
+
return closestDropSlotCandidate(pointer, candidates)?.candidate.id ?? null;
|
|
152
|
+
}
|
|
153
|
+
|
|
65
154
|
export function rebalanceRowWidths(
|
|
66
155
|
panels: SqlPanel[],
|
|
67
156
|
columns: number,
|
|
@@ -276,7 +365,7 @@ export function isDropSlotAvailable(
|
|
|
276
365
|
const rowContainsPanel = row.panels.some((panel) => panel.id === panelId);
|
|
277
366
|
if (rowContainsPanel) return row.panels.length > 1;
|
|
278
367
|
|
|
279
|
-
return row.panels.length <
|
|
368
|
+
return row.panels.length < MAX_DASHBOARD_COLUMNS;
|
|
280
369
|
}
|
|
281
370
|
|
|
282
371
|
export function availableDropSlotIdsForPanel(
|
|
@@ -294,7 +383,9 @@ export function availableDropSlotIdsForPanel(
|
|
|
294
383
|
for (let rowIndex = 0; rowIndex < group.rows.length; rowIndex++) {
|
|
295
384
|
const row = group.rows[rowIndex];
|
|
296
385
|
const rowContainsPanel = row.panels.some((panel) => panel.id === panelId);
|
|
297
|
-
if (!rowContainsPanel && row.panels.length >=
|
|
386
|
+
if (!rowContainsPanel && row.panels.length >= MAX_DASHBOARD_COLUMNS) {
|
|
387
|
+
continue;
|
|
388
|
+
}
|
|
298
389
|
if (rowContainsPanel && row.panels.length <= 1) continue;
|
|
299
390
|
|
|
300
391
|
for (
|
|
@@ -317,6 +408,29 @@ export function availableDropSlotIdsForPanel(
|
|
|
317
408
|
return ids;
|
|
318
409
|
}
|
|
319
410
|
|
|
411
|
+
export function columnExpansionForDropSlot(
|
|
412
|
+
groups: DashboardPanelGroup[],
|
|
413
|
+
panelId: string,
|
|
414
|
+
slot: DashboardDropSlot,
|
|
415
|
+
): DashboardColumnExpansion | null {
|
|
416
|
+
if (slot.type !== "column") return null;
|
|
417
|
+
|
|
418
|
+
const group = groups.find((item) => item.key === slot.groupKey);
|
|
419
|
+
const row = group?.rows[slot.rowIndex];
|
|
420
|
+
if (!group || !row) return null;
|
|
421
|
+
|
|
422
|
+
const rowContainsPanel = row.panels.some((panel) => panel.id === panelId);
|
|
423
|
+
const requiredColumns = rowContainsPanel
|
|
424
|
+
? row.panels.length
|
|
425
|
+
: row.panels.length + 1;
|
|
426
|
+
if (requiredColumns <= group.columns) return null;
|
|
427
|
+
|
|
428
|
+
return {
|
|
429
|
+
columns: clampDashboardColumns(requiredColumns),
|
|
430
|
+
sectionPanelId: group.section?.id ?? null,
|
|
431
|
+
};
|
|
432
|
+
}
|
|
433
|
+
|
|
320
434
|
export function movePanelToDropSlot(
|
|
321
435
|
panels: SqlPanel[],
|
|
322
436
|
panelId: string,
|
|
@@ -408,6 +522,10 @@ export function movePanelToDropSlot(
|
|
|
408
522
|
0,
|
|
409
523
|
movingPanel,
|
|
410
524
|
);
|
|
525
|
+
targetGroup.columns = Math.max(
|
|
526
|
+
targetGroup.columns,
|
|
527
|
+
clampDashboardColumns(targetRow.panels.length),
|
|
528
|
+
);
|
|
411
529
|
}
|
|
412
530
|
|
|
413
531
|
return flattenGroups(nextGroups);
|
|
@@ -17,7 +17,6 @@ import {
|
|
|
17
17
|
useDroppable,
|
|
18
18
|
DndContext,
|
|
19
19
|
DragOverlay,
|
|
20
|
-
pointerWithin,
|
|
21
20
|
PointerSensor,
|
|
22
21
|
useSensor,
|
|
23
22
|
useSensors,
|
|
@@ -106,9 +105,11 @@ import { DashboardSkeleton } from "../DashboardSkeleton";
|
|
|
106
105
|
import {
|
|
107
106
|
availableDropSlotIdsForPanel,
|
|
108
107
|
buildDashboardPanelGroups,
|
|
108
|
+
columnExpansionForDropSlot,
|
|
109
109
|
distanceFromPointerToRect,
|
|
110
110
|
dropSlotId,
|
|
111
111
|
movePanelToDropSlot,
|
|
112
|
+
preferredDropSlotId,
|
|
112
113
|
readDropSlot,
|
|
113
114
|
removePanelFromLayout,
|
|
114
115
|
sameDropSlot,
|
|
@@ -978,6 +979,11 @@ export default function SqlDashboardPage() {
|
|
|
978
979
|
const slot = readDropSlot(event.over?.data.current);
|
|
979
980
|
if (!slot) return;
|
|
980
981
|
const panelId = String(event.active.id);
|
|
982
|
+
const columnExpansion = columnExpansionForDropSlot(
|
|
983
|
+
buildDashboardPanelGroups(dashboard.panels, dashboardColumns),
|
|
984
|
+
panelId,
|
|
985
|
+
slot,
|
|
986
|
+
);
|
|
981
987
|
const nextPanels = movePanelToDropSlot(
|
|
982
988
|
dashboard.panels,
|
|
983
989
|
panelId,
|
|
@@ -985,9 +991,19 @@ export default function SqlDashboardPage() {
|
|
|
985
991
|
dashboardColumns,
|
|
986
992
|
);
|
|
987
993
|
if (nextPanels === dashboard.panels) return;
|
|
994
|
+
const persistedPanels = columnExpansion?.sectionPanelId
|
|
995
|
+
? nextPanels.map((panel) =>
|
|
996
|
+
panel.id === columnExpansion.sectionPanelId
|
|
997
|
+
? { ...panel, columns: columnExpansion.columns }
|
|
998
|
+
: panel,
|
|
999
|
+
)
|
|
1000
|
+
: nextPanels;
|
|
988
1001
|
persist({
|
|
989
1002
|
...dashboard,
|
|
990
|
-
|
|
1003
|
+
...(columnExpansion && !columnExpansion.sectionPanelId
|
|
1004
|
+
? { columns: columnExpansion.columns }
|
|
1005
|
+
: {}),
|
|
1006
|
+
panels: persistedPanels,
|
|
991
1007
|
});
|
|
992
1008
|
},
|
|
993
1009
|
[dashboard, dashboardColumns, persist],
|
|
@@ -1154,36 +1170,43 @@ export default function SqlDashboardPage() {
|
|
|
1154
1170
|
|
|
1155
1171
|
if (droppableContainers.length === 0) return [];
|
|
1156
1172
|
|
|
1157
|
-
const exactCollisions = pointerWithin({
|
|
1158
|
-
...args,
|
|
1159
|
-
droppableContainers,
|
|
1160
|
-
});
|
|
1161
|
-
if (exactCollisions.length > 0) return exactCollisions;
|
|
1162
|
-
|
|
1163
1173
|
if (!args.pointerCoordinates) return [];
|
|
1164
1174
|
|
|
1165
|
-
|
|
1166
|
-
let closestContainer: (typeof droppableContainers)[number] | null = null;
|
|
1167
|
-
let closestValue = Number.MAX_VALUE;
|
|
1168
|
-
for (const droppableContainer of droppableContainers) {
|
|
1175
|
+
const candidates = droppableContainers.flatMap((droppableContainer) => {
|
|
1169
1176
|
const rect = args.droppableRects.get(droppableContainer.id);
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1177
|
+
const slot = readDropSlot(droppableContainer.data.current);
|
|
1178
|
+
if (!rect || !slot) return [];
|
|
1179
|
+
return [
|
|
1180
|
+
{
|
|
1181
|
+
id: String(droppableContainer.id),
|
|
1182
|
+
slot,
|
|
1183
|
+
rect,
|
|
1184
|
+
},
|
|
1185
|
+
];
|
|
1186
|
+
});
|
|
1187
|
+
const preferredId = preferredDropSlotId(
|
|
1188
|
+
args.pointerCoordinates,
|
|
1189
|
+
candidates,
|
|
1190
|
+
);
|
|
1191
|
+
const preferredContainer =
|
|
1192
|
+
preferredId === null
|
|
1193
|
+
? null
|
|
1194
|
+
: (droppableContainers.find(
|
|
1195
|
+
(container) => String(container.id) === preferredId,
|
|
1196
|
+
) ?? null);
|
|
1197
|
+
const preferredRect =
|
|
1198
|
+
preferredContainer && args.droppableRects.get(preferredContainer.id);
|
|
1199
|
+
|
|
1200
|
+
return preferredId && preferredContainer && preferredRect
|
|
1181
1201
|
? [
|
|
1182
1202
|
{
|
|
1183
|
-
id:
|
|
1203
|
+
id: preferredId,
|
|
1184
1204
|
data: {
|
|
1185
|
-
droppableContainer:
|
|
1186
|
-
value:
|
|
1205
|
+
droppableContainer: preferredContainer,
|
|
1206
|
+
value: distanceFromPointerToRect(
|
|
1207
|
+
args.pointerCoordinates,
|
|
1208
|
+
preferredRect,
|
|
1209
|
+
),
|
|
1187
1210
|
},
|
|
1188
1211
|
},
|
|
1189
1212
|
]
|