@burdenoff/microfe-vibecontrols 2026.623.2 → 2026.623.4
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/dist/components/actions/ActionTypeBadge.js +1 -1
- package/dist/components/actions/ActionTypeBadge.js.map +1 -1
- package/dist/components/agent-manager/AgentManagerAdminBar.js +1 -1
- package/dist/components/agent-manager/AgentManagerAdminBar.js.map +1 -1
- package/dist/components/agent-manager/AgentManagerChatPane.js +1 -1
- package/dist/components/agent-manager/AgentManagerChatPane.js.map +1 -1
- package/dist/components/agent-manager/AgentManagerInputArea.js +1 -1
- package/dist/components/agent-manager/AgentManagerInputArea.js.map +1 -1
- package/dist/components/agent-manager/BridleConfigPanel.js +2 -2
- package/dist/components/agent-manager/BridleConfigPanel.js.map +1 -1
- package/dist/components/agent-manager/ProfilesPanel.js +1 -1
- package/dist/components/agent-manager/ProfilesPanel.js.map +1 -1
- package/dist/components/agent-manager/controls/AutonomySelector.js +1 -1
- package/dist/components/agent-manager/controls/AutonomySelector.js.map +1 -1
- package/dist/components/agent-manager/controls/SdkSelector.js +1 -1
- package/dist/components/agent-manager/controls/SdkSelector.js.map +1 -1
- package/dist/components/agent-manager/controls/VoiceToggle.js +1 -1
- package/dist/components/agent-manager/controls/VoiceToggle.js.map +1 -1
- package/dist/components/agent-manager/detail/SessionLogsPanel.js +2 -2
- package/dist/components/agent-manager/detail/SessionLogsPanel.js.map +1 -1
- package/dist/components/agents/AgentForm.js +2 -2
- package/dist/components/agents/AgentForm.js.map +1 -1
- package/dist/components/ai/AIContextsTab.js +1 -1
- package/dist/components/ai/AIContextsTab.js.map +1 -1
- package/dist/components/ai/AIOverviewTab.js +1 -1
- package/dist/components/ai/AIOverviewTab.js.map +1 -1
- package/dist/components/ai/AIPromptsTab.js +1 -1
- package/dist/components/ai/AIPromptsTab.js.map +1 -1
- package/dist/components/ai/AIQueueTab.js +1 -1
- package/dist/components/ai/AIQueueTab.js.map +1 -1
- package/dist/components/ai/AISessionsTab.js +1 -1
- package/dist/components/ai/AISessionsTab.js.map +1 -1
- package/dist/components/audit/AuditLogTable.js +1 -1
- package/dist/components/audit/AuditLogTable.js.map +1 -1
- package/dist/components/docs/DocsPageTree.js +1 -1
- package/dist/components/docs/DocsPageTree.js.map +1 -1
- package/dist/components/graph/ConnectionPermissionsDialog.js +2 -2
- package/dist/components/graph/ConnectionPermissionsDialog.js.map +1 -1
- package/dist/components/graph/MeshDeployWizard.js +3 -3
- package/dist/components/graph/MeshDeployWizard.js.map +1 -1
- package/dist/components/graph/MeshRequestPanel.js +2 -2
- package/dist/components/graph/MeshRequestPanel.js.map +1 -1
- package/dist/components/notes/NoteCard.js +1 -1
- package/dist/components/notes/NoteCard.js.map +1 -1
- package/dist/components/notes/NoteEditor.js +1 -1
- package/dist/components/notes/NoteEditor.js.map +1 -1
- package/dist/components/notes/NoteList.js +1 -1
- package/dist/components/notes/NoteList.js.map +1 -1
- package/dist/components/shared/AgentSetupBanner.js +5 -5
- package/dist/components/shared/AgentSetupBanner.js.map +1 -1
- package/dist/components/shared/InstallTabs.js +17 -56
- package/dist/components/shared/InstallTabs.js.map +1 -1
- package/dist/components/templates/TemplateCard.js +1 -1
- package/dist/components/templates/TemplateCard.js.map +1 -1
- package/dist/components/vibecalendar/types.js +1 -1
- package/dist/components/vibecalendar/types.js.map +1 -1
- package/dist/components/vibedeck/VibeDeckButtonForm.js +1 -1
- package/dist/components/vibedeck/VibeDeckButtonForm.js.map +1 -1
- package/dist/components/vibedeck/VibeDeckExecutionLog.js +2 -2
- package/dist/components/vibedeck/VibeDeckExecutionLog.js.map +1 -1
- package/dist/components/vibedeck/VibeDeckForm.js +1 -1
- package/dist/components/vibedeck/VibeDeckForm.js.map +1 -1
- package/dist/components/vibedeck/VibeDeckGrid.js +1 -1
- package/dist/components/vibedeck/VibeDeckGrid.js.map +1 -1
- package/dist/pages/AgentsPage.js +2 -2
- package/dist/pages/AgentsPage.js.map +1 -1
- package/dist/pages/CatalogDetailPage.js +1 -1
- package/dist/pages/CatalogDetailPage.js.map +1 -1
- package/dist/pages/ComponentDetailPage.js +1 -1
- package/dist/pages/ComponentDetailPage.js.map +1 -1
- package/dist/pages/GettingStartedPage.js +130 -114
- package/dist/pages/GettingStartedPage.js.map +1 -1
- package/dist/pages/OverviewPage.js +10 -10
- package/dist/pages/OverviewPage.js.map +1 -1
- package/dist/pages/PluginsCatalogPage.js +1 -1
- package/dist/pages/PluginsCatalogPage.js.map +1 -1
- package/dist/pages/SessionsPage.js +3 -3
- package/dist/pages/SessionsPage.js.map +1 -1
- package/dist/pages/SharedSessionPage.js +2 -2
- package/dist/pages/SharedSessionPage.js.map +1 -1
- package/dist/pages/TargetsPage.js +2 -2
- package/dist/pages/TargetsPage.js.map +1 -1
- package/dist/pages/TemplateDetailPage.js +2 -2
- package/dist/pages/TemplateDetailPage.js.map +1 -1
- package/dist/pages/VibesPage.js +2 -2
- package/dist/pages/VibesPage.js.map +1 -1
- package/dist/pages/WebhooksPage.js +2 -2
- package/dist/pages/WebhooksPage.js.map +1 -1
- package/dist/pages/ai/AISessionDetailPage.js +2 -2
- package/dist/pages/ai/AISessionDetailPage.js.map +1 -1
- package/dist/translations/en.js +27 -52
- package/dist/translations/en.js.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"VibeDeckButtonForm.js","names":[],"sources":["../../../src/components/vibedeck/VibeDeckButtonForm.tsx"],"sourcesContent":["import { type FC, useState, useMemo, useCallback } from 'react';\nimport { X, Plus, Search, ChevronDown, Unlink } from 'lucide-react';\nimport { useListActionsQuery, useCreateActionMutation } from '@/generated/wspace-operations';\nimport { useTr } from '../../shared/hooks/useTr';\nimport { CommandEditor } from '../common/CommandEditor';\nimport { languageForInterpreter } from '../common/commandHighlight';\nimport { ActionAgentTargetNotice } from '../common/ActionAgentTargetNotice';\n\ninterface Agent {\n id: string;\n name: string;\n isActive: boolean;\n}\n\ninterface ActionData {\n id: string;\n name: string;\n displayName: string | null;\n actionType: string;\n command: string | null;\n script: string | null;\n scriptInterpreter: string | null;\n webhookUrl: string | null;\n webhookMethod: string | null;\n targetAgentId: string | null;\n targetPlatformFilter: string | null;\n workingDirectory: string | null;\n category: string | null;\n}\n\ninterface ButtonFormData {\n label: string;\n icon: string;\n color: string;\n category: string;\n keyboardShortcut: string;\n confirmationRequired: boolean;\n rowSpan: number;\n colSpan: number;\n actionId: string;\n // Temporary fields for creating a new action inline\n actionType: 'COMMAND' | 'SCRIPT' | 'WEBHOOK';\n command: string;\n workingDirectory: string;\n targetAgentId: string;\n script: string;\n scriptInterpreter: string;\n webhookUrl: string;\n webhookMethod: string;\n webhookHeaders: Array<{ key: string; value: string }>;\n webhookBody: string;\n webhookAuthType: string;\n webhookAuthValue: string;\n}\n\ntype ButtonFormInitialData = Partial<Omit<ButtonFormData, 'webhookHeaders'>> & {\n webhookHeaders?: Array<{ key: string; value: string }> | Record<string, string> | null;\n};\n\ninterface VibeDeckButtonFormProps {\n initialData?: ButtonFormInitialData;\n agents: Agent[];\n onSubmit: (data: ButtonFormData) => void;\n onCancel: () => void;\n isLoading: boolean;\n title: string;\n gridColumns?: number;\n gridRows?: number;\n buttonRow?: number;\n buttonColumn?: number;\n}\n\nconst colorOptions = [\n { value: '', label: 'Default' },\n { value: 'red', label: 'Red' },\n { value: 'green', label: 'Green' },\n { value: 'blue', label: 'Blue' },\n { value: 'yellow', label: 'Yellow' },\n { value: 'purple', label: 'Purple' },\n { value: 'orange', label: 'Orange' },\n { value: 'cyan', label: 'Cyan' },\n { value: 'pink', label: 'Pink' },\n];\n\nconst httpMethods = ['GET', 'POST', 'PUT', 'PATCH', 'DELETE'];\n\nconst interpreters = [\n { value: 'bash', label: 'Bash' },\n { value: 'sh', label: 'Shell (sh)' },\n { value: 'zsh', label: 'Zsh' },\n { value: 'python', label: 'Python' },\n { value: 'python3', label: 'Python 3' },\n { value: 'node', label: 'Node.js' },\n { value: 'bun', label: 'Bun' },\n { value: 'ruby', label: 'Ruby' },\n { value: 'perl', label: 'Perl' },\n];\n\nfunction parseHeadersToArray(\n headers: Array<{ key: string; value: string }> | Record<string, string> | null | undefined\n): Array<{ key: string; value: string }> {\n if (!headers) return [];\n if (Array.isArray(headers)) return headers;\n return Object.entries(headers).map(([key, value]) => ({ key, value: String(value) }));\n}\n\nfunction slugify(text: string): string {\n return text\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, '-')\n .replace(/^-|-$/g, '')\n .slice(0, 60);\n}\n\nexport const VibeDeckButtonForm: FC<VibeDeckButtonFormProps> = ({\n initialData,\n agents,\n onSubmit,\n onCancel,\n isLoading,\n title,\n gridColumns,\n gridRows,\n buttonRow,\n buttonColumn,\n}) => {\n const tr = useTr();\n const DEFAULT_MAX_SPAN = 4;\n const maxRowSpan = Math.max(\n 1,\n gridRows != null && buttonRow != null ? gridRows - buttonRow : DEFAULT_MAX_SPAN\n );\n const maxColSpan = Math.max(\n 1,\n gridColumns != null && buttonColumn != null ? gridColumns - buttonColumn : DEFAULT_MAX_SPAN\n );\n\n // Action selection state\n const [selectedActionId, setSelectedActionId] = useState<string>(initialData?.actionId || '');\n const [actionSearchQuery, setActionSearchQuery] = useState('');\n const [isActionDropdownOpen, setIsActionDropdownOpen] = useState(false);\n const [isCreatingNewAction, setIsCreatingNewAction] = useState(false);\n\n // Fetch available actions\n const { data: actionsData } = useListActionsQuery({\n variables: { pagination: { limit: 100 } },\n fetchPolicy: 'cache-and-network',\n });\n const [createActionMutation] = useCreateActionMutation();\n\n const availableActions: ActionData[] = useMemo(\n () =>\n (actionsData?.actions?.items || []).map(\n (a: {\n id: string;\n name: string;\n displayName?: string | null;\n actionType: string;\n command?: string | null;\n script?: string | null;\n scriptInterpreter?: string | null;\n webhookUrl?: string | null;\n webhookMethod?: string | null;\n targetAgentId?: string | null;\n targetPlatformFilter?: string | null;\n workingDirectory?: string | null;\n category?: string | null;\n }) => ({\n id: a.id,\n name: a.name,\n displayName: a.displayName || null,\n actionType: a.actionType,\n command: a.command || null,\n script: a.script || null,\n scriptInterpreter: a.scriptInterpreter || null,\n webhookUrl: a.webhookUrl || null,\n webhookMethod: a.webhookMethod || null,\n targetAgentId: a.targetAgentId || null,\n targetPlatformFilter: a.targetPlatformFilter || null,\n workingDirectory: a.workingDirectory || null,\n category: a.category || null,\n })\n ),\n [actionsData]\n );\n\n const selectedAction = useMemo(\n () => availableActions.find((a) => a.id === selectedActionId) || null,\n [availableActions, selectedActionId]\n );\n\n const filteredActions = useMemo(() => {\n if (!actionSearchQuery.trim()) return availableActions;\n const q = actionSearchQuery.toLowerCase();\n return availableActions.filter(\n (a) =>\n a.name.toLowerCase().includes(q) ||\n (a.displayName && a.displayName.toLowerCase().includes(q)) ||\n a.actionType.toLowerCase().includes(q)\n );\n }, [availableActions, actionSearchQuery]);\n\n const [form, setForm] = useState<ButtonFormData>({\n label: initialData?.label || '',\n icon: initialData?.icon || '',\n color: initialData?.color || '',\n category: initialData?.category || '',\n keyboardShortcut: initialData?.keyboardShortcut || '',\n confirmationRequired: initialData?.confirmationRequired || false,\n rowSpan: Math.min(initialData?.rowSpan || 1, maxRowSpan),\n colSpan: Math.min(initialData?.colSpan || 1, maxColSpan),\n actionId: initialData?.actionId || '',\n // Legacy fields for creating new action\n actionType: initialData?.actionType || 'COMMAND',\n command: initialData?.command || '',\n workingDirectory: initialData?.workingDirectory || '',\n targetAgentId: initialData?.targetAgentId || '',\n script: initialData?.script || '',\n scriptInterpreter: initialData?.scriptInterpreter || 'bash',\n webhookUrl: initialData?.webhookUrl || '',\n webhookMethod: initialData?.webhookMethod || 'POST',\n webhookHeaders: parseHeadersToArray(initialData?.webhookHeaders),\n webhookBody: initialData?.webhookBody || '',\n webhookAuthType: initialData?.webhookAuthType || '',\n webhookAuthValue: initialData?.webhookAuthValue || '',\n });\n\n const handleSelectAction = (action: ActionData) => {\n setSelectedActionId(action.id);\n setIsActionDropdownOpen(false);\n setActionSearchQuery('');\n setIsCreatingNewAction(false);\n setForm((prev) => ({\n ...prev,\n actionId: action.id,\n actionType: action.actionType as 'COMMAND' | 'SCRIPT' | 'WEBHOOK',\n command: action.command || '',\n script: action.script || '',\n scriptInterpreter: action.scriptInterpreter || 'bash',\n webhookUrl: action.webhookUrl || '',\n webhookMethod: action.webhookMethod || 'POST',\n workingDirectory: action.workingDirectory || '',\n targetAgentId: action.targetAgentId || '',\n category: action.category || prev.category,\n }));\n };\n\n const handleUnlinkAction = () => {\n setSelectedActionId('');\n setForm((prev) => ({ ...prev, actionId: '' }));\n };\n\n const addHeader = () => {\n setForm({ ...form, webhookHeaders: [...form.webhookHeaders, { key: '', value: '' }] });\n };\n\n const removeHeader = (index: number) => {\n setForm({ ...form, webhookHeaders: form.webhookHeaders.filter((_, i) => i !== index) });\n };\n\n const updateHeader = (index: number, field: 'key' | 'value', val: string) => {\n const updated = [...form.webhookHeaders];\n updated[index] = { ...updated[index], [field]: val };\n setForm({ ...form, webhookHeaders: updated });\n };\n\n const hasSelectedAction = !!selectedActionId;\n const isActionFieldsReadOnly = hasSelectedAction && !isCreatingNewAction;\n\n const isNewActionValid =\n (form.actionType === 'COMMAND' && form.command.trim()) ||\n (form.actionType === 'SCRIPT' && form.script.trim()) ||\n (form.actionType === 'WEBHOOK' && form.webhookUrl.trim());\n\n const isValid = form.label.trim() && (hasSelectedAction || isNewActionValid);\n\n const handleSubmit = useCallback(\n async (e: React.FormEvent) => {\n e.preventDefault();\n\n // If creating new action inline, create it first\n if (!hasSelectedAction && isCreatingNewAction) {\n try {\n const actionName =\n slugify(form.label || 'vibedeck-action') + '-' + Date.now().toString(36);\n const result = await createActionMutation({\n variables: {\n input: {\n name: actionName,\n displayName: form.label,\n actionType: form.actionType,\n command: form.actionType === 'COMMAND' ? form.command : undefined,\n script: form.actionType === 'SCRIPT' ? form.script : undefined,\n scriptInterpreter:\n form.actionType === 'SCRIPT' ? form.scriptInterpreter : undefined,\n webhookUrl: form.actionType === 'WEBHOOK' ? form.webhookUrl : undefined,\n webhookMethod: form.actionType === 'WEBHOOK' ? form.webhookMethod : undefined,\n webhookBody: form.actionType === 'WEBHOOK' ? form.webhookBody : undefined,\n webhookAuthType: form.actionType === 'WEBHOOK' ? form.webhookAuthType : undefined,\n webhookAuthValue: form.actionType === 'WEBHOOK' ? form.webhookAuthValue : undefined,\n workingDirectory: form.workingDirectory || undefined,\n targetAgentId: form.targetAgentId || undefined,\n category: form.category || undefined,\n confirmationRequired: form.confirmationRequired,\n },\n },\n refetchQueries: ['ListActions'],\n });\n\n const newActionId = result.data?.createAction?.id;\n if (newActionId) {\n onSubmit({ ...form, actionId: newActionId });\n return;\n }\n } catch {\n // Fall through\n }\n }\n\n onSubmit({ ...form, actionId: selectedActionId });\n },\n [form, hasSelectedAction, isCreatingNewAction, selectedActionId, createActionMutation, onSubmit]\n );\n\n // -- Shared CSS classes --\n const inputCls =\n 'w-full px-3 py-2 rounded-lg border border-border-default bg-bg-surface text-text-primary focus:outline-none focus:ring-2 focus:ring-action-primary-bg';\n const readOnlyCls = 'opacity-70 cursor-not-allowed';\n const sectionLabel = 'block text-sm font-medium text-text-secondary mb-1';\n\n return (\n <div className=\"fixed inset-0 z-50 flex items-center justify-center bg-bg-overlay backdrop-blur-sm\">\n <div className=\"bg-bg-elevated border border-border-default rounded-xl shadow-xl w-full max-w-lg mx-4 max-h-[90vh] overflow-y-auto\">\n {/* Header */}\n <div className=\"flex items-center justify-between px-6 py-4 border-b border-border-default sticky top-0 bg-bg-elevated z-10\">\n <h2 className=\"text-lg font-semibold text-text-primary\">{title}</h2>\n <button\n type=\"button\"\n onClick={onCancel}\n className=\"p-1 rounded hover:bg-bg-sunken text-text-tertiary\"\n >\n <X size={18} />\n </button>\n </div>\n\n <form onSubmit={handleSubmit} className=\"p-6 space-y-4\">\n {/* Label */}\n <div>\n <label className={sectionLabel}>\n {tr('vibecontrols.vibedeck.buttonForm.label', 'Label')} *\n </label>\n <input\n aria-label=\"Deploy Staging\"\n type=\"text\"\n value={form.label}\n onChange={(e) => setForm({ ...form, label: e.target.value })}\n className={inputCls}\n placeholder=\"Deploy Staging\"\n required\n />\n </div>\n\n {/* ── Action Selection ── */}\n <div>\n <label className={sectionLabel}>\n {tr('vibecontrols.vibedeck.buttonForm.action', 'Action')} *\n </label>\n\n {/* Selected action card */}\n {selectedAction ? (\n <div className=\"flex items-center gap-2 px-3 py-2.5 rounded-lg border border-[var(--color-accent-soft)] bg-bg-surface\">\n <div className=\"flex-1 min-w-0\">\n <div className=\"text-sm font-medium text-text-primary truncate\">\n {selectedAction.displayName || selectedAction.name}\n </div>\n <div className=\"text-xs text-text-tertiary flex items-center gap-1\">\n <span className=\"inline-flex items-center px-1.5 py-0.5 rounded text-[10px] font-semibold bg-[var(--color-accent-soft)] text-action-primary-bg\">\n {selectedAction.actionType}\n </span>\n {selectedAction.command && (\n <span className=\"font-mono truncate\">\n {selectedAction.command.length > 35\n ? `${selectedAction.command.slice(0, 35)}...`\n : selectedAction.command}\n </span>\n )}\n </div>\n </div>\n <button\n type=\"button\"\n onClick={handleUnlinkAction}\n className=\"p-1.5 rounded hover:bg-bg-sunken text-text-tertiary hover:text-status-error-text\"\n title=\"Change action\"\n >\n <Unlink size={14} />\n </button>\n </div>\n ) : null}\n\n {/* Surface the agent this action runs on (and warn if it's unhealthy\n or gone) right where the action is linked to the button. */}\n {selectedAction && (\n <div className=\"mt-2\">\n <ActionAgentTargetNotice action={selectedAction} />\n </div>\n )}\n\n {!selectedAction && (\n /* Search for existing action or create new */\n <div className=\"space-y-2\">\n <div className=\"relative\">\n <input\n aria-label=\"control\"\n type=\"text\"\n value={actionSearchQuery}\n onChange={(e) => {\n setActionSearchQuery(e.target.value);\n setIsActionDropdownOpen(true);\n setIsCreatingNewAction(false);\n }}\n onFocus={() => setIsActionDropdownOpen(true)}\n onBlur={() => setTimeout(() => setIsActionDropdownOpen(false), 200)}\n className=\"w-full px-3 py-2 rounded-lg border border-border-default bg-bg-surface text-text-primary text-sm focus:outline-none focus:ring-2 focus:ring-action-primary-bg\"\n placeholder={tr(\n 'vibecontrols.vibedeck.buttonForm.searchActions',\n 'Search existing actions...'\n )}\n />\n {isActionDropdownOpen && (\n <div className=\"absolute z-50 mt-1 w-full max-h-48 overflow-y-auto rounded-lg border border-border-default shadow-xl bg-bg-elevated\">\n {filteredActions.length === 0 ? (\n <div className=\"px-3 py-2 text-sm text-text-tertiary\">No actions found</div>\n ) : (\n filteredActions.map((action) => (\n <button\n key={action.id}\n type=\"button\"\n onMouseDown={(e) => e.preventDefault()}\n onClick={() => handleSelectAction(action)}\n className=\"w-full text-left px-3 py-2 hover:bg-bg-sunken\"\n >\n <div className=\"text-sm font-medium\">\n {action.displayName || action.name}\n </div>\n <div className=\"text-xs text-text-tertiary\">{action.actionType}</div>\n </button>\n ))\n )}\n </div>\n )}\n </div>\n\n {!isCreatingNewAction && (\n <button\n type=\"button\"\n onClick={() => setIsCreatingNewAction(true)}\n className=\"flex items-center gap-1.5 text-sm text-action-primary-bg hover:opacity-80\"\n >\n <Plus size={14} />{' '}\n {tr('vibecontrols.vibedeck.buttonForm.createNewAction', 'Create new action')}\n </button>\n )}\n </div>\n )}\n </div>\n\n {/* ── New Action Fields (if creating new) ── */}\n {isCreatingNewAction && (\n <div className=\"p-4 rounded-lg bg-bg-sunken border border-dashed border-border-default space-y-4\">\n <div>\n <label htmlFor=\"vibedeckbuttonform-23\" className={sectionLabel}>\n Action Type\n </label>\n <select\n id=\"vibedeckbuttonform-23\"\n value={form.actionType}\n onChange={(e) => setForm({ ...form, actionType: e.target.value as any })}\n className={inputCls}\n >\n <option value=\"COMMAND\">Command</option>\n <option value=\"SCRIPT\">Script</option>\n <option value=\"WEBHOOK\">Webhook</option>\n </select>\n </div>\n\n {form.actionType === 'COMMAND' && (\n <div>\n <label htmlFor=\"vibedeckbuttonform-22\" className={sectionLabel}>\n Command *\n </label>\n <CommandEditor\n id=\"vibedeckbuttonform-22\"\n aria-label=\"control\"\n language=\"shell\"\n value={form.command}\n onChange={(e) => setForm({ ...form, command: e.target.value })}\n rows={2}\n required\n />\n </div>\n )}\n\n {form.actionType === 'SCRIPT' && (\n <div>\n <label htmlFor=\"vibedeckbuttonform-21\" className={sectionLabel}>\n Script *\n </label>\n <CommandEditor\n id=\"vibedeckbuttonform-21\"\n aria-label=\"control\"\n language={languageForInterpreter(form.scriptInterpreter)}\n value={form.script}\n onChange={(e) => setForm({ ...form, script: e.target.value })}\n rows={4}\n required\n />\n </div>\n )}\n\n {form.actionType === 'WEBHOOK' && (\n <div>\n <label htmlFor=\"vibedeckbuttonform-20\" className={sectionLabel}>\n URL *\n </label>\n <input\n id=\"vibedeckbuttonform-20\"\n aria-label=\"control\"\n type=\"url\"\n value={form.webhookUrl}\n onChange={(e) => setForm({ ...form, webhookUrl: e.target.value })}\n className={inputCls}\n required\n />\n </div>\n )}\n </div>\n )}\n\n {/* ── Appearance ── */}\n <div className=\"grid grid-cols-2 gap-3\">\n <div>\n <label htmlFor=\"vibedeckbuttonform-19\" className={sectionLabel}>\n Color\n </label>\n <select\n id=\"vibedeckbuttonform-19\"\n value={form.color}\n onChange={(e) => setForm({ ...form, color: e.target.value })}\n className={inputCls}\n >\n {colorOptions.map((opt) => (\n <option key={opt.value} value={opt.value}>\n {opt.label}\n </option>\n ))}\n </select>\n </div>\n <div>\n <label htmlFor=\"vibedeckbuttonform-18\" className={sectionLabel}>\n Category\n </label>\n <input\n id=\"vibedeckbuttonform-18\"\n aria-label=\"control\"\n type=\"text\"\n value={form.category}\n onChange={(e) => setForm({ ...form, category: e.target.value })}\n className={inputCls}\n />\n </div>\n </div>\n\n <div className=\"grid grid-cols-2 gap-3\">\n <div className=\"flex items-center gap-2\">\n <input\n aria-label=\"confReq\"\n type=\"checkbox\"\n checked={form.confirmationRequired}\n onChange={(e) => setForm({ ...form, confirmationRequired: e.target.checked })}\n id=\"confReq\"\n />\n <label htmlFor=\"confReq\" className=\"text-sm\">\n {tr('vibecontrols.vibedeck.buttonForm.requireConfirmation', 'Require confirmation')}\n </label>\n </div>\n </div>\n\n {/* Submit */}\n <div className=\"flex justify-end gap-3 pt-2\">\n <button\n type=\"button\"\n onClick={onCancel}\n className=\"px-4 py-2 rounded-lg text-sm text-text-secondary hover:bg-bg-sunken\"\n >\n {tr('vibecontrols.vibedeck.buttonForm.cancel', 'Cancel')}\n </button>\n <button\n type=\"submit\"\n disabled={isLoading || !isValid}\n className=\"px-4 py-2 rounded-lg text-sm bg-action-primary-bg text-action-primary-text hover:opacity-90 disabled:opacity-50\"\n >\n {isLoading\n ? tr('vibecontrols.vibedeck.buttonForm.saving', 'Saving...')\n : tr('vibecontrols.vibedeck.buttonForm.save', 'Save')}\n </button>\n </div>\n </form>\n </div>\n </div>\n );\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"VibeDeckButtonForm.js","names":[],"sources":["../../../src/components/vibedeck/VibeDeckButtonForm.tsx"],"sourcesContent":["import { type FC, useState, useMemo, useCallback } from 'react';\nimport { X, Plus, Search, ChevronDown, Unlink } from 'lucide-react';\nimport { useListActionsQuery, useCreateActionMutation } from '@/generated/wspace-operations';\nimport { useTr } from '../../shared/hooks/useTr';\nimport { CommandEditor } from '../common/CommandEditor';\nimport { languageForInterpreter } from '../common/commandHighlight';\nimport { ActionAgentTargetNotice } from '../common/ActionAgentTargetNotice';\n\ninterface Agent {\n id: string;\n name: string;\n isActive: boolean;\n}\n\ninterface ActionData {\n id: string;\n name: string;\n displayName: string | null;\n actionType: string;\n command: string | null;\n script: string | null;\n scriptInterpreter: string | null;\n webhookUrl: string | null;\n webhookMethod: string | null;\n targetAgentId: string | null;\n targetPlatformFilter: string | null;\n workingDirectory: string | null;\n category: string | null;\n}\n\ninterface ButtonFormData {\n label: string;\n icon: string;\n color: string;\n category: string;\n keyboardShortcut: string;\n confirmationRequired: boolean;\n rowSpan: number;\n colSpan: number;\n actionId: string;\n // Temporary fields for creating a new action inline\n actionType: 'COMMAND' | 'SCRIPT' | 'WEBHOOK';\n command: string;\n workingDirectory: string;\n targetAgentId: string;\n script: string;\n scriptInterpreter: string;\n webhookUrl: string;\n webhookMethod: string;\n webhookHeaders: Array<{ key: string; value: string }>;\n webhookBody: string;\n webhookAuthType: string;\n webhookAuthValue: string;\n}\n\ntype ButtonFormInitialData = Partial<Omit<ButtonFormData, 'webhookHeaders'>> & {\n webhookHeaders?: Array<{ key: string; value: string }> | Record<string, string> | null;\n};\n\ninterface VibeDeckButtonFormProps {\n initialData?: ButtonFormInitialData;\n agents: Agent[];\n onSubmit: (data: ButtonFormData) => void;\n onCancel: () => void;\n isLoading: boolean;\n title: string;\n gridColumns?: number;\n gridRows?: number;\n buttonRow?: number;\n buttonColumn?: number;\n}\n\nconst colorOptions = [\n { value: '', label: 'Default' },\n { value: 'red', label: 'Red' },\n { value: 'green', label: 'Green' },\n { value: 'blue', label: 'Blue' },\n { value: 'yellow', label: 'Yellow' },\n { value: 'purple', label: 'Purple' },\n { value: 'orange', label: 'Orange' },\n { value: 'cyan', label: 'Cyan' },\n { value: 'pink', label: 'Pink' },\n];\n\nconst httpMethods = ['GET', 'POST', 'PUT', 'PATCH', 'DELETE'];\n\nconst interpreters = [\n { value: 'bash', label: 'Bash' },\n { value: 'sh', label: 'Shell (sh)' },\n { value: 'zsh', label: 'Zsh' },\n { value: 'python', label: 'Python' },\n { value: 'python3', label: 'Python 3' },\n { value: 'node', label: 'Node.js' },\n { value: 'bun', label: 'Bun' },\n { value: 'ruby', label: 'Ruby' },\n { value: 'perl', label: 'Perl' },\n];\n\nfunction parseHeadersToArray(\n headers: Array<{ key: string; value: string }> | Record<string, string> | null | undefined\n): Array<{ key: string; value: string }> {\n if (!headers) return [];\n if (Array.isArray(headers)) return headers;\n return Object.entries(headers).map(([key, value]) => ({ key, value: String(value) }));\n}\n\nfunction slugify(text: string): string {\n return text\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, '-')\n .replace(/^-|-$/g, '')\n .slice(0, 60);\n}\n\nexport const VibeDeckButtonForm: FC<VibeDeckButtonFormProps> = ({\n initialData,\n agents,\n onSubmit,\n onCancel,\n isLoading,\n title,\n gridColumns,\n gridRows,\n buttonRow,\n buttonColumn,\n}) => {\n const tr = useTr();\n const DEFAULT_MAX_SPAN = 4;\n const maxRowSpan = Math.max(\n 1,\n gridRows != null && buttonRow != null ? gridRows - buttonRow : DEFAULT_MAX_SPAN\n );\n const maxColSpan = Math.max(\n 1,\n gridColumns != null && buttonColumn != null ? gridColumns - buttonColumn : DEFAULT_MAX_SPAN\n );\n\n // Action selection state\n const [selectedActionId, setSelectedActionId] = useState<string>(initialData?.actionId || '');\n const [actionSearchQuery, setActionSearchQuery] = useState('');\n const [isActionDropdownOpen, setIsActionDropdownOpen] = useState(false);\n const [isCreatingNewAction, setIsCreatingNewAction] = useState(false);\n\n // Fetch available actions\n const { data: actionsData } = useListActionsQuery({\n variables: { pagination: { limit: 100 } },\n fetchPolicy: 'cache-and-network',\n });\n const [createActionMutation] = useCreateActionMutation();\n\n const availableActions: ActionData[] = useMemo(\n () =>\n (actionsData?.actions?.items || []).map(\n (a: {\n id: string;\n name: string;\n displayName?: string | null;\n actionType: string;\n command?: string | null;\n script?: string | null;\n scriptInterpreter?: string | null;\n webhookUrl?: string | null;\n webhookMethod?: string | null;\n targetAgentId?: string | null;\n targetPlatformFilter?: string | null;\n workingDirectory?: string | null;\n category?: string | null;\n }) => ({\n id: a.id,\n name: a.name,\n displayName: a.displayName || null,\n actionType: a.actionType,\n command: a.command || null,\n script: a.script || null,\n scriptInterpreter: a.scriptInterpreter || null,\n webhookUrl: a.webhookUrl || null,\n webhookMethod: a.webhookMethod || null,\n targetAgentId: a.targetAgentId || null,\n targetPlatformFilter: a.targetPlatformFilter || null,\n workingDirectory: a.workingDirectory || null,\n category: a.category || null,\n })\n ),\n [actionsData]\n );\n\n const selectedAction = useMemo(\n () => availableActions.find((a) => a.id === selectedActionId) || null,\n [availableActions, selectedActionId]\n );\n\n const filteredActions = useMemo(() => {\n if (!actionSearchQuery.trim()) return availableActions;\n const q = actionSearchQuery.toLowerCase();\n return availableActions.filter(\n (a) =>\n a.name.toLowerCase().includes(q) ||\n (a.displayName && a.displayName.toLowerCase().includes(q)) ||\n a.actionType.toLowerCase().includes(q)\n );\n }, [availableActions, actionSearchQuery]);\n\n const [form, setForm] = useState<ButtonFormData>({\n label: initialData?.label || '',\n icon: initialData?.icon || '',\n color: initialData?.color || '',\n category: initialData?.category || '',\n keyboardShortcut: initialData?.keyboardShortcut || '',\n confirmationRequired: initialData?.confirmationRequired || false,\n rowSpan: Math.min(initialData?.rowSpan || 1, maxRowSpan),\n colSpan: Math.min(initialData?.colSpan || 1, maxColSpan),\n actionId: initialData?.actionId || '',\n // Legacy fields for creating new action\n actionType: initialData?.actionType || 'COMMAND',\n command: initialData?.command || '',\n workingDirectory: initialData?.workingDirectory || '',\n targetAgentId: initialData?.targetAgentId || '',\n script: initialData?.script || '',\n scriptInterpreter: initialData?.scriptInterpreter || 'bash',\n webhookUrl: initialData?.webhookUrl || '',\n webhookMethod: initialData?.webhookMethod || 'POST',\n webhookHeaders: parseHeadersToArray(initialData?.webhookHeaders),\n webhookBody: initialData?.webhookBody || '',\n webhookAuthType: initialData?.webhookAuthType || '',\n webhookAuthValue: initialData?.webhookAuthValue || '',\n });\n\n const handleSelectAction = (action: ActionData) => {\n setSelectedActionId(action.id);\n setIsActionDropdownOpen(false);\n setActionSearchQuery('');\n setIsCreatingNewAction(false);\n setForm((prev) => ({\n ...prev,\n actionId: action.id,\n actionType: action.actionType as 'COMMAND' | 'SCRIPT' | 'WEBHOOK',\n command: action.command || '',\n script: action.script || '',\n scriptInterpreter: action.scriptInterpreter || 'bash',\n webhookUrl: action.webhookUrl || '',\n webhookMethod: action.webhookMethod || 'POST',\n workingDirectory: action.workingDirectory || '',\n targetAgentId: action.targetAgentId || '',\n category: action.category || prev.category,\n }));\n };\n\n const handleUnlinkAction = () => {\n setSelectedActionId('');\n setForm((prev) => ({ ...prev, actionId: '' }));\n };\n\n const addHeader = () => {\n setForm({ ...form, webhookHeaders: [...form.webhookHeaders, { key: '', value: '' }] });\n };\n\n const removeHeader = (index: number) => {\n setForm({ ...form, webhookHeaders: form.webhookHeaders.filter((_, i) => i !== index) });\n };\n\n const updateHeader = (index: number, field: 'key' | 'value', val: string) => {\n const updated = [...form.webhookHeaders];\n updated[index] = { ...updated[index], [field]: val };\n setForm({ ...form, webhookHeaders: updated });\n };\n\n const hasSelectedAction = !!selectedActionId;\n const isActionFieldsReadOnly = hasSelectedAction && !isCreatingNewAction;\n\n const isNewActionValid =\n (form.actionType === 'COMMAND' && form.command.trim()) ||\n (form.actionType === 'SCRIPT' && form.script.trim()) ||\n (form.actionType === 'WEBHOOK' && form.webhookUrl.trim());\n\n const isValid = form.label.trim() && (hasSelectedAction || isNewActionValid);\n\n const handleSubmit = useCallback(\n async (e: React.FormEvent) => {\n e.preventDefault();\n\n // If creating new action inline, create it first\n if (!hasSelectedAction && isCreatingNewAction) {\n try {\n const actionName =\n slugify(form.label || 'vibedeck-action') + '-' + Date.now().toString(36);\n const result = await createActionMutation({\n variables: {\n input: {\n name: actionName,\n displayName: form.label,\n actionType: form.actionType,\n command: form.actionType === 'COMMAND' ? form.command : undefined,\n script: form.actionType === 'SCRIPT' ? form.script : undefined,\n scriptInterpreter:\n form.actionType === 'SCRIPT' ? form.scriptInterpreter : undefined,\n webhookUrl: form.actionType === 'WEBHOOK' ? form.webhookUrl : undefined,\n webhookMethod: form.actionType === 'WEBHOOK' ? form.webhookMethod : undefined,\n webhookBody: form.actionType === 'WEBHOOK' ? form.webhookBody : undefined,\n webhookAuthType: form.actionType === 'WEBHOOK' ? form.webhookAuthType : undefined,\n webhookAuthValue: form.actionType === 'WEBHOOK' ? form.webhookAuthValue : undefined,\n workingDirectory: form.workingDirectory || undefined,\n targetAgentId: form.targetAgentId || undefined,\n category: form.category || undefined,\n confirmationRequired: form.confirmationRequired,\n },\n },\n refetchQueries: ['ListActions'],\n });\n\n const newActionId = result.data?.createAction?.id;\n if (newActionId) {\n onSubmit({ ...form, actionId: newActionId });\n return;\n }\n } catch {\n // Fall through\n }\n }\n\n onSubmit({ ...form, actionId: selectedActionId });\n },\n [form, hasSelectedAction, isCreatingNewAction, selectedActionId, createActionMutation, onSubmit]\n );\n\n // -- Shared CSS classes --\n const inputCls =\n 'w-full px-3 py-2 rounded-lg border border-border-default bg-bg-surface text-text-primary focus:outline-none focus:ring-2 focus:ring-action-primary-bg';\n const readOnlyCls = 'opacity-70 cursor-not-allowed';\n const sectionLabel = 'block text-sm font-medium text-text-secondary mb-1';\n\n return (\n <div className=\"fixed inset-0 z-50 flex items-center justify-center bg-bg-overlay backdrop-blur-sm\">\n <div className=\"bg-bg-elevated border border-border-default rounded-xl shadow-xl w-full max-w-lg mx-4 max-h-[90vh] overflow-y-auto\">\n {/* Header */}\n <div className=\"flex items-center justify-between px-6 py-4 border-b border-border-default sticky top-0 bg-bg-elevated z-10\">\n <h2 className=\"text-lg font-semibold text-text-primary\">{title}</h2>\n <button\n type=\"button\"\n onClick={onCancel}\n className=\"p-1 rounded hover:bg-bg-sunken text-text-tertiary\"\n >\n <X size={18} />\n </button>\n </div>\n\n <form onSubmit={handleSubmit} className=\"p-6 space-y-4\">\n {/* Label */}\n <div>\n <label className={sectionLabel}>\n {tr('vibecontrols.vibedeck.buttonForm.label', 'Label')} *\n </label>\n <input\n aria-label=\"Deploy Staging\"\n type=\"text\"\n value={form.label}\n onChange={(e) => setForm({ ...form, label: e.target.value })}\n className={inputCls}\n placeholder=\"Deploy Staging\"\n required\n />\n </div>\n\n {/* ── Action Selection ── */}\n <div>\n <label className={sectionLabel}>\n {tr('vibecontrols.vibedeck.buttonForm.action', 'Action')} *\n </label>\n\n {/* Selected action card */}\n {selectedAction ? (\n <div className=\"flex items-center gap-2 px-3 py-2.5 rounded-lg border border-[var(--color-accent-soft)] bg-bg-surface\">\n <div className=\"flex-1 min-w-0\">\n <div className=\"text-sm font-medium text-text-primary truncate\">\n {selectedAction.displayName || selectedAction.name}\n </div>\n <div className=\"text-xs text-text-tertiary flex items-center gap-1\">\n <span className=\"inline-flex items-center px-1.5 py-0.5 rounded text-[10px] font-semibold bg-[var(--color-accent-soft)] text-text-primary\">\n {selectedAction.actionType}\n </span>\n {selectedAction.command && (\n <span className=\"font-mono truncate\">\n {selectedAction.command.length > 35\n ? `${selectedAction.command.slice(0, 35)}...`\n : selectedAction.command}\n </span>\n )}\n </div>\n </div>\n <button\n type=\"button\"\n onClick={handleUnlinkAction}\n className=\"p-1.5 rounded hover:bg-bg-sunken text-text-tertiary hover:text-status-error-text\"\n title=\"Change action\"\n >\n <Unlink size={14} />\n </button>\n </div>\n ) : null}\n\n {/* Surface the agent this action runs on (and warn if it's unhealthy\n or gone) right where the action is linked to the button. */}\n {selectedAction && (\n <div className=\"mt-2\">\n <ActionAgentTargetNotice action={selectedAction} />\n </div>\n )}\n\n {!selectedAction && (\n /* Search for existing action or create new */\n <div className=\"space-y-2\">\n <div className=\"relative\">\n <input\n aria-label=\"control\"\n type=\"text\"\n value={actionSearchQuery}\n onChange={(e) => {\n setActionSearchQuery(e.target.value);\n setIsActionDropdownOpen(true);\n setIsCreatingNewAction(false);\n }}\n onFocus={() => setIsActionDropdownOpen(true)}\n onBlur={() => setTimeout(() => setIsActionDropdownOpen(false), 200)}\n className=\"w-full px-3 py-2 rounded-lg border border-border-default bg-bg-surface text-text-primary text-sm focus:outline-none focus:ring-2 focus:ring-action-primary-bg\"\n placeholder={tr(\n 'vibecontrols.vibedeck.buttonForm.searchActions',\n 'Search existing actions...'\n )}\n />\n {isActionDropdownOpen && (\n <div className=\"absolute z-50 mt-1 w-full max-h-48 overflow-y-auto rounded-lg border border-border-default shadow-xl bg-bg-elevated\">\n {filteredActions.length === 0 ? (\n <div className=\"px-3 py-2 text-sm text-text-tertiary\">No actions found</div>\n ) : (\n filteredActions.map((action) => (\n <button\n key={action.id}\n type=\"button\"\n onMouseDown={(e) => e.preventDefault()}\n onClick={() => handleSelectAction(action)}\n className=\"w-full text-left px-3 py-2 hover:bg-bg-sunken\"\n >\n <div className=\"text-sm font-medium\">\n {action.displayName || action.name}\n </div>\n <div className=\"text-xs text-text-tertiary\">{action.actionType}</div>\n </button>\n ))\n )}\n </div>\n )}\n </div>\n\n {!isCreatingNewAction && (\n <button\n type=\"button\"\n onClick={() => setIsCreatingNewAction(true)}\n className=\"flex items-center gap-1.5 text-sm text-action-primary-bg hover:opacity-80\"\n >\n <Plus size={14} />{' '}\n {tr('vibecontrols.vibedeck.buttonForm.createNewAction', 'Create new action')}\n </button>\n )}\n </div>\n )}\n </div>\n\n {/* ── New Action Fields (if creating new) ── */}\n {isCreatingNewAction && (\n <div className=\"p-4 rounded-lg bg-bg-sunken border border-dashed border-border-default space-y-4\">\n <div>\n <label htmlFor=\"vibedeckbuttonform-23\" className={sectionLabel}>\n Action Type\n </label>\n <select\n id=\"vibedeckbuttonform-23\"\n value={form.actionType}\n onChange={(e) => setForm({ ...form, actionType: e.target.value as any })}\n className={inputCls}\n >\n <option value=\"COMMAND\">Command</option>\n <option value=\"SCRIPT\">Script</option>\n <option value=\"WEBHOOK\">Webhook</option>\n </select>\n </div>\n\n {form.actionType === 'COMMAND' && (\n <div>\n <label htmlFor=\"vibedeckbuttonform-22\" className={sectionLabel}>\n Command *\n </label>\n <CommandEditor\n id=\"vibedeckbuttonform-22\"\n aria-label=\"control\"\n language=\"shell\"\n value={form.command}\n onChange={(e) => setForm({ ...form, command: e.target.value })}\n rows={2}\n required\n />\n </div>\n )}\n\n {form.actionType === 'SCRIPT' && (\n <div>\n <label htmlFor=\"vibedeckbuttonform-21\" className={sectionLabel}>\n Script *\n </label>\n <CommandEditor\n id=\"vibedeckbuttonform-21\"\n aria-label=\"control\"\n language={languageForInterpreter(form.scriptInterpreter)}\n value={form.script}\n onChange={(e) => setForm({ ...form, script: e.target.value })}\n rows={4}\n required\n />\n </div>\n )}\n\n {form.actionType === 'WEBHOOK' && (\n <div>\n <label htmlFor=\"vibedeckbuttonform-20\" className={sectionLabel}>\n URL *\n </label>\n <input\n id=\"vibedeckbuttonform-20\"\n aria-label=\"control\"\n type=\"url\"\n value={form.webhookUrl}\n onChange={(e) => setForm({ ...form, webhookUrl: e.target.value })}\n className={inputCls}\n required\n />\n </div>\n )}\n </div>\n )}\n\n {/* ── Appearance ── */}\n <div className=\"grid grid-cols-2 gap-3\">\n <div>\n <label htmlFor=\"vibedeckbuttonform-19\" className={sectionLabel}>\n Color\n </label>\n <select\n id=\"vibedeckbuttonform-19\"\n value={form.color}\n onChange={(e) => setForm({ ...form, color: e.target.value })}\n className={inputCls}\n >\n {colorOptions.map((opt) => (\n <option key={opt.value} value={opt.value}>\n {opt.label}\n </option>\n ))}\n </select>\n </div>\n <div>\n <label htmlFor=\"vibedeckbuttonform-18\" className={sectionLabel}>\n Category\n </label>\n <input\n id=\"vibedeckbuttonform-18\"\n aria-label=\"control\"\n type=\"text\"\n value={form.category}\n onChange={(e) => setForm({ ...form, category: e.target.value })}\n className={inputCls}\n />\n </div>\n </div>\n\n <div className=\"grid grid-cols-2 gap-3\">\n <div className=\"flex items-center gap-2\">\n <input\n aria-label=\"confReq\"\n type=\"checkbox\"\n checked={form.confirmationRequired}\n onChange={(e) => setForm({ ...form, confirmationRequired: e.target.checked })}\n id=\"confReq\"\n />\n <label htmlFor=\"confReq\" className=\"text-sm\">\n {tr('vibecontrols.vibedeck.buttonForm.requireConfirmation', 'Require confirmation')}\n </label>\n </div>\n </div>\n\n {/* Submit */}\n <div className=\"flex justify-end gap-3 pt-2\">\n <button\n type=\"button\"\n onClick={onCancel}\n className=\"px-4 py-2 rounded-lg text-sm text-text-secondary hover:bg-bg-sunken\"\n >\n {tr('vibecontrols.vibedeck.buttonForm.cancel', 'Cancel')}\n </button>\n <button\n type=\"submit\"\n disabled={isLoading || !isValid}\n className=\"px-4 py-2 rounded-lg text-sm bg-action-primary-bg text-action-primary-text hover:opacity-90 disabled:opacity-50\"\n >\n {isLoading\n ? tr('vibecontrols.vibedeck.buttonForm.saving', 'Saving...')\n : tr('vibecontrols.vibedeck.buttonForm.save', 'Save')}\n </button>\n </div>\n </form>\n </div>\n </div>\n );\n};\n"],"mappings":"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"}
|
|
@@ -56,7 +56,7 @@ var y = ({ status: e }) => {
|
|
|
56
56
|
error: "Failed"
|
|
57
57
|
}, S = {
|
|
58
58
|
pending: "bg-bg-sunken text-text-tertiary",
|
|
59
|
-
running: "bg-[var(--color-accent-soft)] text-
|
|
59
|
+
running: "bg-[var(--color-accent-soft)] text-text-primary",
|
|
60
60
|
success: "bg-status-success-bg-subtle text-status-success-text",
|
|
61
61
|
error: "bg-status-error-bg-subtle text-status-error-text"
|
|
62
62
|
}, C = ({ entries: o, expanded: s, onToggleExpanded: c, onClear: C }) => {
|
|
@@ -86,7 +86,7 @@ var y = ({ status: e }) => {
|
|
|
86
86
|
className: "flex items-center gap-2",
|
|
87
87
|
children: [
|
|
88
88
|
E > 0 && /* @__PURE__ */ g("span", {
|
|
89
|
-
className: "inline-flex items-center gap-1 px-1.5 py-0.5 rounded-full text-[10px] font-bold bg-[var(--color-accent-soft)] text-
|
|
89
|
+
className: "inline-flex items-center gap-1 px-1.5 py-0.5 rounded-full text-[10px] font-bold bg-[var(--color-accent-soft)] text-text-primary",
|
|
90
90
|
children: [/* @__PURE__ */ h(l, {
|
|
91
91
|
size: 10,
|
|
92
92
|
className: "animate-spin"
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"VibeDeckExecutionLog.js","names":[],"sources":["../../../src/components/vibedeck/VibeDeckExecutionLog.tsx"],"sourcesContent":["import { type FC, useRef, useEffect } from 'react';\nimport { motion, AnimatePresence } from 'framer-motion';\nimport {\n Terminal,\n Globe,\n FileCode,\n Check,\n X,\n Loader2,\n Clock,\n ChevronDown,\n ChevronUp,\n Trash2,\n} from 'lucide-react';\nimport { useTr } from '../../shared/hooks/useTr';\n\nexport interface ExecutionLogEntry {\n id: string;\n buttonId: string;\n buttonLabel: string;\n actionType: 'COMMAND' | 'SCRIPT' | 'WEBHOOK';\n command?: string | null;\n status: 'pending' | 'running' | 'success' | 'error';\n output?: string | null;\n exitCode?: number | null;\n webhookStatusCode?: number | null;\n startedAt: Date;\n completedAt?: Date | null;\n error?: string | null;\n}\n\ninterface VibeDeckExecutionLogProps {\n entries: ExecutionLogEntry[];\n expanded: boolean;\n onToggleExpanded: () => void;\n onClear: () => void;\n}\n\nfunction formatDuration(start: Date, end?: Date | null): string {\n const ms = (end ? end.getTime() : Date.now()) - start.getTime();\n if (ms < 1000) return `${ms}ms`;\n if (ms < 60000) return `${(ms / 1000).toFixed(1)}s`;\n return `${Math.floor(ms / 60000)}m ${Math.floor((ms % 60000) / 1000)}s`;\n}\n\nfunction formatTime(date: Date): string {\n return date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', second: '2-digit' });\n}\n\nconst StatusIcon: FC<{ status: ExecutionLogEntry['status'] }> = ({ status }) => {\n switch (status) {\n case 'pending':\n return <Clock size={14} className=\"text-text-tertiary\" />;\n case 'running':\n return <Loader2 size={14} className=\"text-action-primary-bg animate-spin\" />;\n case 'success':\n return <Check size={14} className=\"text-status-success-text\" />;\n case 'error':\n return <X size={14} className=\"text-status-error-text\" />;\n }\n};\n\nconst ActionTypeIcon: FC<{ type: string }> = ({ type }) => {\n switch (type) {\n case 'WEBHOOK':\n return <Globe size={12} className=\"text-text-tertiary\" />;\n case 'SCRIPT':\n return <FileCode size={12} className=\"text-text-tertiary\" />;\n default:\n return <Terminal size={12} className=\"text-text-tertiary\" />;\n }\n};\n\nconst statusLabel: Record<string, string> = {\n pending: 'Pending',\n running: 'Running',\n success: 'Success',\n error: 'Failed',\n};\n\nconst statusBadgeClass: Record<string, string> = {\n pending: 'bg-bg-sunken text-text-tertiary',\n running: 'bg-[var(--color-accent-soft)] text-action-primary-bg',\n success: 'bg-status-success-bg-subtle text-status-success-text',\n error: 'bg-status-error-bg-subtle text-status-error-text',\n};\n\nexport const VibeDeckExecutionLog: FC<VibeDeckExecutionLogProps> = ({\n entries,\n expanded,\n onToggleExpanded,\n onClear,\n}) => {\n const tr = useTr();\n const scrollRef = useRef<HTMLDivElement>(null);\n\n // Auto-scroll to bottom when new entries arrive\n useEffect(() => {\n if (scrollRef.current && expanded) {\n scrollRef.current.scrollTop = scrollRef.current.scrollHeight;\n }\n }, [entries.length, expanded]);\n\n if (entries.length === 0) return null;\n\n const runningCount = entries.filter((e) => e.status === 'running').length;\n const successCount = entries.filter((e) => e.status === 'success').length;\n const errorCount = entries.filter((e) => e.status === 'error').length;\n\n return (\n <div className=\"rounded-lg border border-border-default bg-bg-surface overflow-hidden\">\n {/* Header bar — always visible */}\n <button\n type=\"button\"\n onClick={onToggleExpanded}\n className=\"w-full flex items-center justify-between px-4 py-2.5 hover:bg-bg-sunken transition-colors\"\n >\n <div className=\"flex items-center gap-3\">\n <Terminal size={14} className=\"text-text-secondary\" />\n <span className=\"text-sm font-medium text-text-primary\">\n {tr('vibecontrols.vibedeck.executionLog.title', 'Execution Log')}\n </span>\n <div className=\"flex items-center gap-2\">\n {runningCount > 0 && (\n <span className=\"inline-flex items-center gap-1 px-1.5 py-0.5 rounded-full text-[10px] font-bold bg-[var(--color-accent-soft)] text-action-primary-bg\">\n <Loader2 size={10} className=\"animate-spin\" />\n {runningCount}\n </span>\n )}\n {successCount > 0 && (\n <span className=\"inline-flex items-center gap-1 px-1.5 py-0.5 rounded-full text-[10px] font-bold bg-status-success-bg-subtle text-status-success-text\">\n <Check size={10} />\n {successCount}\n </span>\n )}\n {errorCount > 0 && (\n <span className=\"inline-flex items-center gap-1 px-1.5 py-0.5 rounded-full text-[10px] font-bold bg-status-error-bg-subtle text-status-error-text\">\n <X size={10} />\n {errorCount}\n </span>\n )}\n <span className=\"text-xs text-text-tertiary\">{entries.length} total</span>\n </div>\n </div>\n <div className=\"flex items-center gap-2\">\n {expanded && (\n <span\n role=\"button\"\n tabIndex={0}\n onKeyDown={(e) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n onClear();\n }\n }}\n onClick={(e) => {\n e.stopPropagation();\n onClear();\n }}\n className=\"p-1 rounded hover:bg-bg-sunken text-text-tertiary hover:text-text-primary transition-colors\"\n title=\"Clear log\"\n >\n <Trash2 size={12} />\n </span>\n )}\n {expanded ? (\n <ChevronDown size={14} className=\"text-text-tertiary\" />\n ) : (\n <ChevronUp size={14} className=\"text-text-tertiary\" />\n )}\n </div>\n </button>\n\n {/* Log entries — expandable */}\n <AnimatePresence>\n {expanded && (\n <motion.div\n initial={{ height: 0, opacity: 0 }}\n animate={{ height: 'auto', opacity: 1 }}\n exit={{ height: 0, opacity: 0 }}\n transition={{ duration: 0.2 }}\n className=\"overflow-hidden\"\n >\n <div className=\"border-t border-border-default\" />\n <div\n ref={scrollRef}\n className=\"max-h-[240px] overflow-y-auto divide-y divide-border-default/50\"\n >\n {entries.map((entry) => (\n <div\n key={entry.id}\n className={`px-4 py-2.5 flex items-start gap-3 text-sm transition-colors ${\n entry.status === 'running' ? 'bg-[var(--color-accent-soft)]' : ''\n }`}\n >\n {/* Status icon */}\n <div className=\"pt-0.5 shrink-0\">\n <StatusIcon status={entry.status} />\n </div>\n\n {/* Main content */}\n <div className=\"flex-1 min-w-0\">\n <div className=\"flex items-center gap-2\">\n <span className=\"font-medium text-text-primary truncate\">\n {entry.buttonLabel}\n </span>\n <span\n className={`inline-flex items-center px-1.5 py-0.5 rounded text-[10px] font-bold uppercase ${statusBadgeClass[entry.status]}`}\n >\n {statusLabel[entry.status]}\n </span>\n <span className=\"flex items-center gap-1 text-text-tertiary\">\n <ActionTypeIcon type={entry.actionType} />\n </span>\n </div>\n\n {/* Command or detail */}\n {entry.command && (\n <div className=\"mt-0.5 text-xs text-text-tertiary font-mono truncate\">\n {entry.command}\n </div>\n )}\n\n {/* Output (collapsed, show first line) */}\n {entry.output && (\n <pre className=\"mt-1 text-[11px] font-mono text-text-secondary bg-bg-sunken rounded px-2 py-1 max-h-16 overflow-y-auto whitespace-pre-wrap\">\n {entry.output}\n </pre>\n )}\n\n {/* Error message */}\n {entry.error && (\n <div className=\"mt-1 text-xs text-status-error-text font-mono\">\n {entry.error}\n </div>\n )}\n </div>\n\n {/* Timing */}\n <div className=\"shrink-0 text-right\">\n <div className=\"text-[10px] text-text-tertiary\">\n {formatTime(entry.startedAt)}\n </div>\n <div className=\"text-[10px] text-text-tertiary font-mono\">\n {formatDuration(entry.startedAt, entry.completedAt)}\n </div>\n {entry.exitCode != null && entry.exitCode !== 0 && (\n <div className=\"text-[10px] text-status-error-text font-mono\">\n exit {entry.exitCode}\n </div>\n )}\n {entry.webhookStatusCode != null && (\n <div\n className={`text-[10px] font-mono ${entry.webhookStatusCode >= 400 ? 'text-status-error-text' : 'text-text-tertiary'}`}\n >\n HTTP {entry.webhookStatusCode}\n </div>\n )}\n </div>\n </div>\n ))}\n </div>\n </motion.div>\n )}\n </AnimatePresence>\n </div>\n );\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"VibeDeckExecutionLog.js","names":[],"sources":["../../../src/components/vibedeck/VibeDeckExecutionLog.tsx"],"sourcesContent":["import { type FC, useRef, useEffect } from 'react';\nimport { motion, AnimatePresence } from 'framer-motion';\nimport {\n Terminal,\n Globe,\n FileCode,\n Check,\n X,\n Loader2,\n Clock,\n ChevronDown,\n ChevronUp,\n Trash2,\n} from 'lucide-react';\nimport { useTr } from '../../shared/hooks/useTr';\n\nexport interface ExecutionLogEntry {\n id: string;\n buttonId: string;\n buttonLabel: string;\n actionType: 'COMMAND' | 'SCRIPT' | 'WEBHOOK';\n command?: string | null;\n status: 'pending' | 'running' | 'success' | 'error';\n output?: string | null;\n exitCode?: number | null;\n webhookStatusCode?: number | null;\n startedAt: Date;\n completedAt?: Date | null;\n error?: string | null;\n}\n\ninterface VibeDeckExecutionLogProps {\n entries: ExecutionLogEntry[];\n expanded: boolean;\n onToggleExpanded: () => void;\n onClear: () => void;\n}\n\nfunction formatDuration(start: Date, end?: Date | null): string {\n const ms = (end ? end.getTime() : Date.now()) - start.getTime();\n if (ms < 1000) return `${ms}ms`;\n if (ms < 60000) return `${(ms / 1000).toFixed(1)}s`;\n return `${Math.floor(ms / 60000)}m ${Math.floor((ms % 60000) / 1000)}s`;\n}\n\nfunction formatTime(date: Date): string {\n return date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', second: '2-digit' });\n}\n\nconst StatusIcon: FC<{ status: ExecutionLogEntry['status'] }> = ({ status }) => {\n switch (status) {\n case 'pending':\n return <Clock size={14} className=\"text-text-tertiary\" />;\n case 'running':\n return <Loader2 size={14} className=\"text-action-primary-bg animate-spin\" />;\n case 'success':\n return <Check size={14} className=\"text-status-success-text\" />;\n case 'error':\n return <X size={14} className=\"text-status-error-text\" />;\n }\n};\n\nconst ActionTypeIcon: FC<{ type: string }> = ({ type }) => {\n switch (type) {\n case 'WEBHOOK':\n return <Globe size={12} className=\"text-text-tertiary\" />;\n case 'SCRIPT':\n return <FileCode size={12} className=\"text-text-tertiary\" />;\n default:\n return <Terminal size={12} className=\"text-text-tertiary\" />;\n }\n};\n\nconst statusLabel: Record<string, string> = {\n pending: 'Pending',\n running: 'Running',\n success: 'Success',\n error: 'Failed',\n};\n\nconst statusBadgeClass: Record<string, string> = {\n pending: 'bg-bg-sunken text-text-tertiary',\n running: 'bg-[var(--color-accent-soft)] text-text-primary',\n success: 'bg-status-success-bg-subtle text-status-success-text',\n error: 'bg-status-error-bg-subtle text-status-error-text',\n};\n\nexport const VibeDeckExecutionLog: FC<VibeDeckExecutionLogProps> = ({\n entries,\n expanded,\n onToggleExpanded,\n onClear,\n}) => {\n const tr = useTr();\n const scrollRef = useRef<HTMLDivElement>(null);\n\n // Auto-scroll to bottom when new entries arrive\n useEffect(() => {\n if (scrollRef.current && expanded) {\n scrollRef.current.scrollTop = scrollRef.current.scrollHeight;\n }\n }, [entries.length, expanded]);\n\n if (entries.length === 0) return null;\n\n const runningCount = entries.filter((e) => e.status === 'running').length;\n const successCount = entries.filter((e) => e.status === 'success').length;\n const errorCount = entries.filter((e) => e.status === 'error').length;\n\n return (\n <div className=\"rounded-lg border border-border-default bg-bg-surface overflow-hidden\">\n {/* Header bar — always visible */}\n <button\n type=\"button\"\n onClick={onToggleExpanded}\n className=\"w-full flex items-center justify-between px-4 py-2.5 hover:bg-bg-sunken transition-colors\"\n >\n <div className=\"flex items-center gap-3\">\n <Terminal size={14} className=\"text-text-secondary\" />\n <span className=\"text-sm font-medium text-text-primary\">\n {tr('vibecontrols.vibedeck.executionLog.title', 'Execution Log')}\n </span>\n <div className=\"flex items-center gap-2\">\n {runningCount > 0 && (\n <span className=\"inline-flex items-center gap-1 px-1.5 py-0.5 rounded-full text-[10px] font-bold bg-[var(--color-accent-soft)] text-text-primary\">\n <Loader2 size={10} className=\"animate-spin\" />\n {runningCount}\n </span>\n )}\n {successCount > 0 && (\n <span className=\"inline-flex items-center gap-1 px-1.5 py-0.5 rounded-full text-[10px] font-bold bg-status-success-bg-subtle text-status-success-text\">\n <Check size={10} />\n {successCount}\n </span>\n )}\n {errorCount > 0 && (\n <span className=\"inline-flex items-center gap-1 px-1.5 py-0.5 rounded-full text-[10px] font-bold bg-status-error-bg-subtle text-status-error-text\">\n <X size={10} />\n {errorCount}\n </span>\n )}\n <span className=\"text-xs text-text-tertiary\">{entries.length} total</span>\n </div>\n </div>\n <div className=\"flex items-center gap-2\">\n {expanded && (\n <span\n role=\"button\"\n tabIndex={0}\n onKeyDown={(e) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n onClear();\n }\n }}\n onClick={(e) => {\n e.stopPropagation();\n onClear();\n }}\n className=\"p-1 rounded hover:bg-bg-sunken text-text-tertiary hover:text-text-primary transition-colors\"\n title=\"Clear log\"\n >\n <Trash2 size={12} />\n </span>\n )}\n {expanded ? (\n <ChevronDown size={14} className=\"text-text-tertiary\" />\n ) : (\n <ChevronUp size={14} className=\"text-text-tertiary\" />\n )}\n </div>\n </button>\n\n {/* Log entries — expandable */}\n <AnimatePresence>\n {expanded && (\n <motion.div\n initial={{ height: 0, opacity: 0 }}\n animate={{ height: 'auto', opacity: 1 }}\n exit={{ height: 0, opacity: 0 }}\n transition={{ duration: 0.2 }}\n className=\"overflow-hidden\"\n >\n <div className=\"border-t border-border-default\" />\n <div\n ref={scrollRef}\n className=\"max-h-[240px] overflow-y-auto divide-y divide-border-default/50\"\n >\n {entries.map((entry) => (\n <div\n key={entry.id}\n className={`px-4 py-2.5 flex items-start gap-3 text-sm transition-colors ${\n entry.status === 'running' ? 'bg-[var(--color-accent-soft)]' : ''\n }`}\n >\n {/* Status icon */}\n <div className=\"pt-0.5 shrink-0\">\n <StatusIcon status={entry.status} />\n </div>\n\n {/* Main content */}\n <div className=\"flex-1 min-w-0\">\n <div className=\"flex items-center gap-2\">\n <span className=\"font-medium text-text-primary truncate\">\n {entry.buttonLabel}\n </span>\n <span\n className={`inline-flex items-center px-1.5 py-0.5 rounded text-[10px] font-bold uppercase ${statusBadgeClass[entry.status]}`}\n >\n {statusLabel[entry.status]}\n </span>\n <span className=\"flex items-center gap-1 text-text-tertiary\">\n <ActionTypeIcon type={entry.actionType} />\n </span>\n </div>\n\n {/* Command or detail */}\n {entry.command && (\n <div className=\"mt-0.5 text-xs text-text-tertiary font-mono truncate\">\n {entry.command}\n </div>\n )}\n\n {/* Output (collapsed, show first line) */}\n {entry.output && (\n <pre className=\"mt-1 text-[11px] font-mono text-text-secondary bg-bg-sunken rounded px-2 py-1 max-h-16 overflow-y-auto whitespace-pre-wrap\">\n {entry.output}\n </pre>\n )}\n\n {/* Error message */}\n {entry.error && (\n <div className=\"mt-1 text-xs text-status-error-text font-mono\">\n {entry.error}\n </div>\n )}\n </div>\n\n {/* Timing */}\n <div className=\"shrink-0 text-right\">\n <div className=\"text-[10px] text-text-tertiary\">\n {formatTime(entry.startedAt)}\n </div>\n <div className=\"text-[10px] text-text-tertiary font-mono\">\n {formatDuration(entry.startedAt, entry.completedAt)}\n </div>\n {entry.exitCode != null && entry.exitCode !== 0 && (\n <div className=\"text-[10px] text-status-error-text font-mono\">\n exit {entry.exitCode}\n </div>\n )}\n {entry.webhookStatusCode != null && (\n <div\n className={`text-[10px] font-mono ${entry.webhookStatusCode >= 400 ? 'text-status-error-text' : 'text-text-tertiary'}`}\n >\n HTTP {entry.webhookStatusCode}\n </div>\n )}\n </div>\n </div>\n ))}\n </div>\n </motion.div>\n )}\n </AnimatePresence>\n </div>\n );\n};\n"],"mappings":"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"}
|
|
@@ -170,7 +170,7 @@ var a = [
|
|
|
170
170
|
gridColumns: e.cols,
|
|
171
171
|
gridRows: e.rows
|
|
172
172
|
}),
|
|
173
|
-
className: `px-3 py-1.5 rounded-lg text-sm border transition-colors ${g.gridColumns === e.cols && g.gridRows === e.rows ? "border-action-primary-bg bg-[var(--color-accent-soft)] text-
|
|
173
|
+
className: `px-3 py-1.5 rounded-lg text-sm border transition-colors ${g.gridColumns === e.cols && g.gridRows === e.rows ? "border-action-primary-bg bg-[var(--color-accent-soft)] text-text-primary" : "border-border-default text-text-secondary hover:bg-bg-sunken"}`,
|
|
174
174
|
children: e.label
|
|
175
175
|
}, e.label))
|
|
176
176
|
}),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"VibeDeckForm.js","names":[],"sources":["../../../src/components/vibedeck/VibeDeckForm.tsx"],"sourcesContent":["import { type FC, type ReactNode, useState } from 'react';\nimport { X } from 'lucide-react';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\n\ninterface VibeDeckFormData {\n name: string;\n description: string;\n icon: string;\n colorTheme: string;\n gridColumns: number;\n gridRows: number;\n buttonSize: string;\n}\n\ninterface VibeDeckFormProps {\n initialData?: Partial<VibeDeckFormData>;\n onSubmit: (data: VibeDeckFormData) => void;\n onCancel: () => void;\n isLoading: boolean;\n title: string;\n /** Optional extra content rendered below the form fields (e.g. tag picker in edit mode) */\n extraSection?: ReactNode;\n}\n\nconst colorOptionKeys: { value: string; labelKey: string; fallback: string }[] = [\n { value: '', labelKey: 'vibeDeck.colorDefault', fallback: 'Default' },\n { value: 'blue', labelKey: 'vibeDeck.colorBlue', fallback: 'Blue' },\n { value: 'green', labelKey: 'vibeDeck.colorGreen', fallback: 'Green' },\n { value: 'red', labelKey: 'vibeDeck.colorRed', fallback: 'Red' },\n { value: 'purple', labelKey: 'vibeDeck.colorPurple', fallback: 'Purple' },\n { value: 'orange', labelKey: 'vibeDeck.colorOrange', fallback: 'Orange' },\n { value: 'cyan', labelKey: 'vibeDeck.colorCyan', fallback: 'Cyan' },\n { value: 'pink', labelKey: 'vibeDeck.colorPink', fallback: 'Pink' },\n];\n\nconst gridPresets = [\n { label: '3x3', cols: 3, rows: 3 },\n { label: '4x3', cols: 4, rows: 3 },\n { label: '4x4', cols: 4, rows: 4 },\n { label: '5x3', cols: 5, rows: 3 },\n { label: '5x4', cols: 5, rows: 4 },\n { label: '6x4', cols: 6, rows: 4 },\n];\n\nexport const VibeDeckForm: FC<VibeDeckFormProps> = ({\n initialData,\n onSubmit,\n onCancel,\n isLoading,\n title,\n extraSection,\n}) => {\n const { t } = useI18n();\n const tr = (key: string, fallback: string) => {\n const v = t(key);\n return v === key ? fallback : v;\n };\n\n const colorOptions = colorOptionKeys.map((opt) => ({\n value: opt.value,\n label: tr(opt.labelKey, opt.fallback),\n }));\n\n const [form, setForm] = useState<VibeDeckFormData>({\n name: initialData?.name || '',\n description: initialData?.description || '',\n icon: initialData?.icon || '',\n colorTheme: initialData?.colorTheme || '',\n gridColumns: initialData?.gridColumns || 4,\n gridRows: initialData?.gridRows || 3,\n buttonSize: initialData?.buttonSize || 'md',\n });\n\n const handleSubmit = (e: React.FormEvent) => {\n e.preventDefault();\n onSubmit(form);\n };\n\n return (\n <div className=\"fixed inset-0 z-50 flex items-center justify-center bg-bg-overlay backdrop-blur-sm\">\n <div className=\"bg-bg-elevated border border-border-default rounded-xl shadow-xl w-full max-w-md mx-4\">\n <div className=\"flex items-center justify-between px-6 py-4 border-b border-border-default\">\n <h2 className=\"text-lg font-semibold text-text-primary\">{title}</h2>\n <button\n type=\"button\"\n onClick={onCancel}\n className=\"p-1 rounded hover:bg-bg-sunken text-text-tertiary\"\n >\n <X size={18} />\n </button>\n </div>\n\n <form onSubmit={handleSubmit} className=\"p-6 space-y-4\">\n <div>\n <label className=\"block text-sm font-medium text-text-secondary mb-1\">\n {tr('vibeDeck.name', 'Name')} *\n </label>\n <input\n aria-label=\"control\"\n type=\"text\"\n value={form.name}\n onChange={(e) => setForm({ ...form, name: e.target.value })}\n className=\"w-full px-3 py-2 rounded-lg border border-border-default bg-bg-surface text-text-primary focus:outline-none focus:ring-2 focus:ring-action-primary-bg\"\n placeholder={tr('vibeDeck.namePlaceholder', 'My Vibe Deck')}\n required\n />\n </div>\n\n <div>\n <label className=\"block text-sm font-medium text-text-secondary mb-1\">\n {tr('vibeDeck.description', 'Description')}\n </label>\n <textarea\n aria-label=\"control\"\n value={form.description}\n onChange={(e) => setForm({ ...form, description: e.target.value })}\n className=\"w-full px-3 py-2 rounded-lg border border-border-default bg-bg-surface text-text-primary focus:outline-none focus:ring-2 focus:ring-action-primary-bg resize-none\"\n rows={2}\n placeholder={tr('vibeDeck.descriptionPlaceholder', 'Optional description...')}\n />\n </div>\n\n <div>\n <label className=\"block text-sm font-medium text-text-secondary mb-1\">\n {tr('vibeDeck.colorTheme', 'Color Theme')}\n </label>\n <select\n value={form.colorTheme}\n onChange={(e) => setForm({ ...form, colorTheme: e.target.value })}\n className=\"w-full px-3 py-2 rounded-lg border border-border-default bg-bg-surface text-text-primary focus:outline-none focus:ring-2 focus:ring-action-primary-bg\"\n >\n {colorOptions.map((opt) => (\n <option key={opt.value} value={opt.value}>\n {opt.label}\n </option>\n ))}\n </select>\n </div>\n\n <div>\n <label className=\"block text-sm font-medium text-text-secondary mb-2\">\n {tr('vibeDeck.gridLayout', 'Grid Layout')}\n </label>\n <div className=\"flex flex-wrap gap-2\">\n {gridPresets.map((preset) => {\n const isActive = form.gridColumns === preset.cols && form.gridRows === preset.rows;\n return (\n <button\n key={preset.label}\n type=\"button\"\n onClick={() =>\n setForm({ ...form, gridColumns: preset.cols, gridRows: preset.rows })\n }\n className={`px-3 py-1.5 rounded-lg text-sm border transition-colors ${\n isActive\n ? 'border-action-primary-bg bg-[var(--color-accent-soft)] text-action-primary-bg'\n : 'border-border-default text-text-secondary hover:bg-bg-sunken'\n }`}\n >\n {preset.label}\n </button>\n );\n })}\n </div>\n <div className=\"flex gap-2 mt-2\">\n <div className=\"flex-1\">\n <label className=\"text-xs text-text-tertiary\">\n {tr('vibeDeck.columns', 'Columns')}\n </label>\n <input\n aria-label=\"control\"\n type=\"number\"\n min={1}\n max={10}\n value={form.gridColumns}\n onChange={(e) => setForm({ ...form, gridColumns: parseInt(e.target.value) || 4 })}\n className=\"w-full px-2 py-1 rounded border border-border-default bg-bg-surface text-text-primary text-sm\"\n />\n </div>\n <div className=\"flex-1\">\n <label className=\"text-xs text-text-tertiary\">{tr('vibeDeck.rows', 'Rows')}</label>\n <input\n aria-label=\"control\"\n type=\"number\"\n min={1}\n max={10}\n value={form.gridRows}\n onChange={(e) => setForm({ ...form, gridRows: parseInt(e.target.value) || 3 })}\n className=\"w-full px-2 py-1 rounded border border-border-default bg-bg-surface text-text-primary text-sm\"\n />\n </div>\n </div>\n </div>\n\n {extraSection && (\n <div className=\"border-t border-border-default pt-4\">{extraSection}</div>\n )}\n\n <div className=\"flex justify-end gap-3 pt-2\">\n <button\n type=\"button\"\n onClick={onCancel}\n className=\"px-4 py-2 rounded-lg text-sm text-text-secondary hover:bg-bg-sunken transition-colors\"\n >\n {tr('vibeDeck.cancel', 'Cancel')}\n </button>\n <button\n type=\"submit\"\n disabled={isLoading || !form.name.trim()}\n className=\"px-4 py-2 rounded-lg text-sm bg-action-primary-bg text-action-primary-text hover:opacity-90 transition-opacity disabled:opacity-50\"\n >\n {isLoading ? tr('vibeDeck.saving', 'Saving...') : tr('vibeDeck.save', 'Save')}\n </button>\n </div>\n </form>\n </div>\n </div>\n );\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"VibeDeckForm.js","names":[],"sources":["../../../src/components/vibedeck/VibeDeckForm.tsx"],"sourcesContent":["import { type FC, type ReactNode, useState } from 'react';\nimport { X } from 'lucide-react';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\n\ninterface VibeDeckFormData {\n name: string;\n description: string;\n icon: string;\n colorTheme: string;\n gridColumns: number;\n gridRows: number;\n buttonSize: string;\n}\n\ninterface VibeDeckFormProps {\n initialData?: Partial<VibeDeckFormData>;\n onSubmit: (data: VibeDeckFormData) => void;\n onCancel: () => void;\n isLoading: boolean;\n title: string;\n /** Optional extra content rendered below the form fields (e.g. tag picker in edit mode) */\n extraSection?: ReactNode;\n}\n\nconst colorOptionKeys: { value: string; labelKey: string; fallback: string }[] = [\n { value: '', labelKey: 'vibeDeck.colorDefault', fallback: 'Default' },\n { value: 'blue', labelKey: 'vibeDeck.colorBlue', fallback: 'Blue' },\n { value: 'green', labelKey: 'vibeDeck.colorGreen', fallback: 'Green' },\n { value: 'red', labelKey: 'vibeDeck.colorRed', fallback: 'Red' },\n { value: 'purple', labelKey: 'vibeDeck.colorPurple', fallback: 'Purple' },\n { value: 'orange', labelKey: 'vibeDeck.colorOrange', fallback: 'Orange' },\n { value: 'cyan', labelKey: 'vibeDeck.colorCyan', fallback: 'Cyan' },\n { value: 'pink', labelKey: 'vibeDeck.colorPink', fallback: 'Pink' },\n];\n\nconst gridPresets = [\n { label: '3x3', cols: 3, rows: 3 },\n { label: '4x3', cols: 4, rows: 3 },\n { label: '4x4', cols: 4, rows: 4 },\n { label: '5x3', cols: 5, rows: 3 },\n { label: '5x4', cols: 5, rows: 4 },\n { label: '6x4', cols: 6, rows: 4 },\n];\n\nexport const VibeDeckForm: FC<VibeDeckFormProps> = ({\n initialData,\n onSubmit,\n onCancel,\n isLoading,\n title,\n extraSection,\n}) => {\n const { t } = useI18n();\n const tr = (key: string, fallback: string) => {\n const v = t(key);\n return v === key ? fallback : v;\n };\n\n const colorOptions = colorOptionKeys.map((opt) => ({\n value: opt.value,\n label: tr(opt.labelKey, opt.fallback),\n }));\n\n const [form, setForm] = useState<VibeDeckFormData>({\n name: initialData?.name || '',\n description: initialData?.description || '',\n icon: initialData?.icon || '',\n colorTheme: initialData?.colorTheme || '',\n gridColumns: initialData?.gridColumns || 4,\n gridRows: initialData?.gridRows || 3,\n buttonSize: initialData?.buttonSize || 'md',\n });\n\n const handleSubmit = (e: React.FormEvent) => {\n e.preventDefault();\n onSubmit(form);\n };\n\n return (\n <div className=\"fixed inset-0 z-50 flex items-center justify-center bg-bg-overlay backdrop-blur-sm\">\n <div className=\"bg-bg-elevated border border-border-default rounded-xl shadow-xl w-full max-w-md mx-4\">\n <div className=\"flex items-center justify-between px-6 py-4 border-b border-border-default\">\n <h2 className=\"text-lg font-semibold text-text-primary\">{title}</h2>\n <button\n type=\"button\"\n onClick={onCancel}\n className=\"p-1 rounded hover:bg-bg-sunken text-text-tertiary\"\n >\n <X size={18} />\n </button>\n </div>\n\n <form onSubmit={handleSubmit} className=\"p-6 space-y-4\">\n <div>\n <label className=\"block text-sm font-medium text-text-secondary mb-1\">\n {tr('vibeDeck.name', 'Name')} *\n </label>\n <input\n aria-label=\"control\"\n type=\"text\"\n value={form.name}\n onChange={(e) => setForm({ ...form, name: e.target.value })}\n className=\"w-full px-3 py-2 rounded-lg border border-border-default bg-bg-surface text-text-primary focus:outline-none focus:ring-2 focus:ring-action-primary-bg\"\n placeholder={tr('vibeDeck.namePlaceholder', 'My Vibe Deck')}\n required\n />\n </div>\n\n <div>\n <label className=\"block text-sm font-medium text-text-secondary mb-1\">\n {tr('vibeDeck.description', 'Description')}\n </label>\n <textarea\n aria-label=\"control\"\n value={form.description}\n onChange={(e) => setForm({ ...form, description: e.target.value })}\n className=\"w-full px-3 py-2 rounded-lg border border-border-default bg-bg-surface text-text-primary focus:outline-none focus:ring-2 focus:ring-action-primary-bg resize-none\"\n rows={2}\n placeholder={tr('vibeDeck.descriptionPlaceholder', 'Optional description...')}\n />\n </div>\n\n <div>\n <label className=\"block text-sm font-medium text-text-secondary mb-1\">\n {tr('vibeDeck.colorTheme', 'Color Theme')}\n </label>\n <select\n value={form.colorTheme}\n onChange={(e) => setForm({ ...form, colorTheme: e.target.value })}\n className=\"w-full px-3 py-2 rounded-lg border border-border-default bg-bg-surface text-text-primary focus:outline-none focus:ring-2 focus:ring-action-primary-bg\"\n >\n {colorOptions.map((opt) => (\n <option key={opt.value} value={opt.value}>\n {opt.label}\n </option>\n ))}\n </select>\n </div>\n\n <div>\n <label className=\"block text-sm font-medium text-text-secondary mb-2\">\n {tr('vibeDeck.gridLayout', 'Grid Layout')}\n </label>\n <div className=\"flex flex-wrap gap-2\">\n {gridPresets.map((preset) => {\n const isActive = form.gridColumns === preset.cols && form.gridRows === preset.rows;\n return (\n <button\n key={preset.label}\n type=\"button\"\n onClick={() =>\n setForm({ ...form, gridColumns: preset.cols, gridRows: preset.rows })\n }\n className={`px-3 py-1.5 rounded-lg text-sm border transition-colors ${\n isActive\n ? 'border-action-primary-bg bg-[var(--color-accent-soft)] text-text-primary'\n : 'border-border-default text-text-secondary hover:bg-bg-sunken'\n }`}\n >\n {preset.label}\n </button>\n );\n })}\n </div>\n <div className=\"flex gap-2 mt-2\">\n <div className=\"flex-1\">\n <label className=\"text-xs text-text-tertiary\">\n {tr('vibeDeck.columns', 'Columns')}\n </label>\n <input\n aria-label=\"control\"\n type=\"number\"\n min={1}\n max={10}\n value={form.gridColumns}\n onChange={(e) => setForm({ ...form, gridColumns: parseInt(e.target.value) || 4 })}\n className=\"w-full px-2 py-1 rounded border border-border-default bg-bg-surface text-text-primary text-sm\"\n />\n </div>\n <div className=\"flex-1\">\n <label className=\"text-xs text-text-tertiary\">{tr('vibeDeck.rows', 'Rows')}</label>\n <input\n aria-label=\"control\"\n type=\"number\"\n min={1}\n max={10}\n value={form.gridRows}\n onChange={(e) => setForm({ ...form, gridRows: parseInt(e.target.value) || 3 })}\n className=\"w-full px-2 py-1 rounded border border-border-default bg-bg-surface text-text-primary text-sm\"\n />\n </div>\n </div>\n </div>\n\n {extraSection && (\n <div className=\"border-t border-border-default pt-4\">{extraSection}</div>\n )}\n\n <div className=\"flex justify-end gap-3 pt-2\">\n <button\n type=\"button\"\n onClick={onCancel}\n className=\"px-4 py-2 rounded-lg text-sm text-text-secondary hover:bg-bg-sunken transition-colors\"\n >\n {tr('vibeDeck.cancel', 'Cancel')}\n </button>\n <button\n type=\"submit\"\n disabled={isLoading || !form.name.trim()}\n className=\"px-4 py-2 rounded-lg text-sm bg-action-primary-bg text-action-primary-text hover:opacity-90 transition-opacity disabled:opacity-50\"\n >\n {isLoading ? tr('vibeDeck.saving', 'Saving...') : tr('vibeDeck.save', 'Save')}\n </button>\n </div>\n </form>\n </div>\n </div>\n );\n};\n"],"mappings":"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"}
|
|
@@ -100,7 +100,7 @@ var s = ({ gridColumns: s, gridRows: c, buttons: l, onExecuteButton: u, onEditBu
|
|
|
100
100
|
onDragLeave: A,
|
|
101
101
|
onDrop: (e) => j(e, t, n),
|
|
102
102
|
children: C.draggingId ? /* @__PURE__ */ o("div", {
|
|
103
|
-
className: `w-full h-full min-h-[80px] rounded-lg border-2 border-dashed flex items-center justify-center transition-all ${e ? "border-action-primary-bg bg-[var(--color-accent-soft)] text-
|
|
103
|
+
className: `w-full h-full min-h-[80px] rounded-lg border-2 border-dashed flex items-center justify-center transition-all ${e ? "border-action-primary-bg bg-[var(--color-accent-soft)] text-text-primary" : "border-border-default/30 text-text-tertiary/30"}`,
|
|
104
104
|
children: e && /* @__PURE__ */ o("span", {
|
|
105
105
|
className: "text-xs font-medium",
|
|
106
106
|
children: S("vibeDeck.dropHere", "Drop here")
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"VibeDeckGrid.js","names":[],"sources":["../../../src/components/vibedeck/VibeDeckGrid.tsx"],"sourcesContent":["import { type FC, type ReactElement, useState, useCallback, useMemo } from 'react';\nimport { Plus } from 'lucide-react';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { VibeDeckButtonTile } from './VibeDeckButtonTile';\n\ninterface ActionRef {\n id: string;\n name: string;\n displayName: string | null;\n actionType: string;\n command: string | null;\n script: string | null;\n webhookUrl: string | null;\n targetAgentId: string | null;\n}\n\ninterface ButtonData {\n id: string;\n label: string;\n icon: string | null;\n color: string | null;\n command: string | null;\n actionType: string;\n category: string | null;\n confirmationRequired: boolean;\n keyboardShortcut: string | null;\n targetAgentId: string | null;\n scriptInterpreter: string | null;\n webhookUrl: string | null;\n webhookMethod: string | null;\n actionId?: string | null;\n action?: ActionRef | null;\n row: number;\n column: number;\n rowSpan: number;\n colSpan: number;\n}\n\ninterface VibeDeckGridProps {\n gridColumns: number;\n gridRows: number;\n buttons: ButtonData[];\n onExecuteButton: (id: string) => void;\n onEditButton: (id: string) => void;\n onDeleteButton: (id: string) => void;\n onDropButton: (buttonId: string, targetRow: number, targetColumn: number) => void;\n onAddButton: (row: number, column: number) => void;\n executingButtonId: string | null;\n canCreate: boolean;\n canUpdate: boolean;\n canDelete: boolean;\n canExecute: boolean;\n /**\n * When true, the grid is in Preview mode — tile clicks execute (and route\n * through the parent's confirmation check for buttons with\n * `confirmationRequired: true`). Defaults to false so the tile behaves as a\n * static editor surface.\n */\n previewMode?: boolean;\n}\n\nexport const VibeDeckGrid: FC<VibeDeckGridProps> = ({\n gridColumns,\n gridRows,\n buttons,\n onExecuteButton,\n onEditButton,\n onDeleteButton,\n onDropButton,\n onAddButton,\n executingButtonId,\n canCreate,\n canUpdate,\n canDelete,\n canExecute,\n previewMode = false,\n}) => {\n const { t } = useI18n();\n const tr = (key: string, fallback: string) => {\n const v = t(key);\n return v === key ? fallback : v;\n };\n\n const [dragState, setDragState] = useState<{\n draggingId: string | null;\n targetKey: string | null;\n }>({ draggingId: null, targetKey: null });\n\n // Build a map of occupied cells (memoized to stabilize hook dependencies)\n const { occupiedCells, buttonAtCell } = useMemo(() => {\n const occupied = new Set<string>();\n const cellMap = new Map<string, ButtonData>();\n for (const btn of buttons) {\n for (let r = btn.row; r < btn.row + btn.rowSpan; r++) {\n for (let c = btn.column; c < btn.column + btn.colSpan; c++) {\n occupied.add(`${r},${c}`);\n }\n }\n cellMap.set(`${btn.row},${btn.column}`, btn);\n }\n return { occupiedCells: occupied, buttonAtCell: cellMap };\n }, [buttons]);\n\n const handleDragStart = useCallback((e: React.DragEvent, buttonId: string) => {\n e.dataTransfer.setData('text/plain', buttonId);\n e.dataTransfer.effectAllowed = 'move';\n setDragState({ draggingId: buttonId, targetKey: null });\n }, []);\n\n const isDropValid = useCallback(\n (targetRow: number, targetCol: number) => {\n if (!dragState.draggingId) return false;\n const btn = buttons.find((b) => b.id === dragState.draggingId);\n if (!btn) return false;\n // Check grid boundary\n if (targetRow + btn.rowSpan > gridRows || targetCol + btn.colSpan > gridColumns) return false;\n // Check collision with other buttons\n for (let r = targetRow; r < targetRow + btn.rowSpan; r++) {\n for (let c = targetCol; c < targetCol + btn.colSpan; c++) {\n if (occupiedCells.has(`${r},${c}`)) {\n // Allow overlap with the dragged button's own current footprint\n const isOwnCell =\n r >= btn.row &&\n r < btn.row + btn.rowSpan &&\n c >= btn.column &&\n c < btn.column + btn.colSpan;\n if (!isOwnCell) return false;\n }\n }\n }\n return true;\n },\n [dragState.draggingId, buttons, gridRows, gridColumns, occupiedCells]\n );\n\n const handleDragOver = useCallback(\n (e: React.DragEvent, key: string, row: number, col: number) => {\n e.preventDefault();\n e.dataTransfer.dropEffect = 'move';\n if (isDropValid(row, col)) {\n setDragState((prev) => ({ ...prev, targetKey: key }));\n } else {\n setDragState((prev) => ({ ...prev, targetKey: null }));\n }\n },\n [isDropValid]\n );\n\n const handleDragLeave = useCallback(() => {\n setDragState((prev) => ({ ...prev, targetKey: null }));\n }, []);\n\n const handleDrop = useCallback(\n (e: React.DragEvent, row: number, col: number) => {\n e.preventDefault();\n const buttonId = e.dataTransfer.getData('text/plain');\n setDragState({ draggingId: null, targetKey: null });\n if (buttonId && isDropValid(row, col)) {\n onDropButton(buttonId, row, col);\n }\n },\n [onDropButton, isDropValid]\n );\n\n const handleDragEnd = useCallback(() => {\n setDragState({ draggingId: null, targetKey: null });\n }, []);\n\n // Build grid cells\n const cells: ReactElement[] = [];\n for (let row = 0; row < gridRows; row++) {\n for (let col = 0; col < gridColumns; col++) {\n const key = `${row},${col}`;\n const button = buttonAtCell.get(key);\n\n if (button) {\n const isDragging = dragState.draggingId === button.id;\n cells.push(\n <div\n key={key}\n style={{\n gridRow: `${row + 1} / span ${button.rowSpan}`,\n gridColumn: `${col + 1} / span ${button.colSpan}`,\n }}\n draggable={canUpdate}\n onDragStart={(e) => handleDragStart(e, button.id)}\n onDragEnd={handleDragEnd}\n className={`${isDragging ? 'opacity-40' : ''} ${canUpdate ? 'cursor-grab active:cursor-grabbing' : ''}`}\n >\n <VibeDeckButtonTile\n button={button}\n onExecute={onExecuteButton}\n onEdit={onEditButton}\n onDelete={onDeleteButton}\n canUpdate={canUpdate}\n canDelete={canDelete}\n canExecute={canExecute}\n executing={executingButtonId === button.id}\n previewMode={previewMode}\n />\n </div>\n );\n } else if (!occupiedCells.has(key)) {\n const isDropTarget = dragState.draggingId && dragState.targetKey === key;\n cells.push(\n <div\n key={key}\n style={{ gridRow: row + 1, gridColumn: col + 1 }}\n onDragOver={(e) => handleDragOver(e, key, row, col)}\n onDragLeave={handleDragLeave}\n onDrop={(e) => handleDrop(e, row, col)}\n >\n {dragState.draggingId ? (\n <div\n className={`w-full h-full min-h-[80px] rounded-lg border-2 border-dashed flex items-center justify-center transition-all ${\n isDropTarget\n ? 'border-action-primary-bg bg-[var(--color-accent-soft)] text-action-primary-bg'\n : 'border-border-default/30 text-text-tertiary/30'\n }`}\n >\n {isDropTarget && (\n <span className=\"text-xs font-medium\">\n {tr('vibeDeck.dropHere', 'Drop here')}\n </span>\n )}\n </div>\n ) : canCreate ? (\n <button\n type=\"button\"\n onClick={() => onAddButton(row, col)}\n className=\"w-full h-full min-h-[80px] rounded-lg border-2 border-dashed border-border-strong text-text-tertiary hover:border-border-focus hover:text-text-secondary flex items-center justify-center transition-all hover:bg-bg-sunken\"\n >\n <Plus size={18} />\n </button>\n ) : (\n <div className=\"w-full h-full min-h-[80px] rounded-lg border-2 border-dashed border-border-default\" />\n )}\n </div>\n );\n }\n }\n }\n\n return (\n <div\n className=\"grid gap-3\"\n style={{\n gridTemplateColumns: `repeat(${gridColumns}, 1fr)`,\n gridTemplateRows: `repeat(${gridRows}, auto)`,\n }}\n >\n {cells}\n </div>\n );\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"VibeDeckGrid.js","names":[],"sources":["../../../src/components/vibedeck/VibeDeckGrid.tsx"],"sourcesContent":["import { type FC, type ReactElement, useState, useCallback, useMemo } from 'react';\nimport { Plus } from 'lucide-react';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { VibeDeckButtonTile } from './VibeDeckButtonTile';\n\ninterface ActionRef {\n id: string;\n name: string;\n displayName: string | null;\n actionType: string;\n command: string | null;\n script: string | null;\n webhookUrl: string | null;\n targetAgentId: string | null;\n}\n\ninterface ButtonData {\n id: string;\n label: string;\n icon: string | null;\n color: string | null;\n command: string | null;\n actionType: string;\n category: string | null;\n confirmationRequired: boolean;\n keyboardShortcut: string | null;\n targetAgentId: string | null;\n scriptInterpreter: string | null;\n webhookUrl: string | null;\n webhookMethod: string | null;\n actionId?: string | null;\n action?: ActionRef | null;\n row: number;\n column: number;\n rowSpan: number;\n colSpan: number;\n}\n\ninterface VibeDeckGridProps {\n gridColumns: number;\n gridRows: number;\n buttons: ButtonData[];\n onExecuteButton: (id: string) => void;\n onEditButton: (id: string) => void;\n onDeleteButton: (id: string) => void;\n onDropButton: (buttonId: string, targetRow: number, targetColumn: number) => void;\n onAddButton: (row: number, column: number) => void;\n executingButtonId: string | null;\n canCreate: boolean;\n canUpdate: boolean;\n canDelete: boolean;\n canExecute: boolean;\n /**\n * When true, the grid is in Preview mode — tile clicks execute (and route\n * through the parent's confirmation check for buttons with\n * `confirmationRequired: true`). Defaults to false so the tile behaves as a\n * static editor surface.\n */\n previewMode?: boolean;\n}\n\nexport const VibeDeckGrid: FC<VibeDeckGridProps> = ({\n gridColumns,\n gridRows,\n buttons,\n onExecuteButton,\n onEditButton,\n onDeleteButton,\n onDropButton,\n onAddButton,\n executingButtonId,\n canCreate,\n canUpdate,\n canDelete,\n canExecute,\n previewMode = false,\n}) => {\n const { t } = useI18n();\n const tr = (key: string, fallback: string) => {\n const v = t(key);\n return v === key ? fallback : v;\n };\n\n const [dragState, setDragState] = useState<{\n draggingId: string | null;\n targetKey: string | null;\n }>({ draggingId: null, targetKey: null });\n\n // Build a map of occupied cells (memoized to stabilize hook dependencies)\n const { occupiedCells, buttonAtCell } = useMemo(() => {\n const occupied = new Set<string>();\n const cellMap = new Map<string, ButtonData>();\n for (const btn of buttons) {\n for (let r = btn.row; r < btn.row + btn.rowSpan; r++) {\n for (let c = btn.column; c < btn.column + btn.colSpan; c++) {\n occupied.add(`${r},${c}`);\n }\n }\n cellMap.set(`${btn.row},${btn.column}`, btn);\n }\n return { occupiedCells: occupied, buttonAtCell: cellMap };\n }, [buttons]);\n\n const handleDragStart = useCallback((e: React.DragEvent, buttonId: string) => {\n e.dataTransfer.setData('text/plain', buttonId);\n e.dataTransfer.effectAllowed = 'move';\n setDragState({ draggingId: buttonId, targetKey: null });\n }, []);\n\n const isDropValid = useCallback(\n (targetRow: number, targetCol: number) => {\n if (!dragState.draggingId) return false;\n const btn = buttons.find((b) => b.id === dragState.draggingId);\n if (!btn) return false;\n // Check grid boundary\n if (targetRow + btn.rowSpan > gridRows || targetCol + btn.colSpan > gridColumns) return false;\n // Check collision with other buttons\n for (let r = targetRow; r < targetRow + btn.rowSpan; r++) {\n for (let c = targetCol; c < targetCol + btn.colSpan; c++) {\n if (occupiedCells.has(`${r},${c}`)) {\n // Allow overlap with the dragged button's own current footprint\n const isOwnCell =\n r >= btn.row &&\n r < btn.row + btn.rowSpan &&\n c >= btn.column &&\n c < btn.column + btn.colSpan;\n if (!isOwnCell) return false;\n }\n }\n }\n return true;\n },\n [dragState.draggingId, buttons, gridRows, gridColumns, occupiedCells]\n );\n\n const handleDragOver = useCallback(\n (e: React.DragEvent, key: string, row: number, col: number) => {\n e.preventDefault();\n e.dataTransfer.dropEffect = 'move';\n if (isDropValid(row, col)) {\n setDragState((prev) => ({ ...prev, targetKey: key }));\n } else {\n setDragState((prev) => ({ ...prev, targetKey: null }));\n }\n },\n [isDropValid]\n );\n\n const handleDragLeave = useCallback(() => {\n setDragState((prev) => ({ ...prev, targetKey: null }));\n }, []);\n\n const handleDrop = useCallback(\n (e: React.DragEvent, row: number, col: number) => {\n e.preventDefault();\n const buttonId = e.dataTransfer.getData('text/plain');\n setDragState({ draggingId: null, targetKey: null });\n if (buttonId && isDropValid(row, col)) {\n onDropButton(buttonId, row, col);\n }\n },\n [onDropButton, isDropValid]\n );\n\n const handleDragEnd = useCallback(() => {\n setDragState({ draggingId: null, targetKey: null });\n }, []);\n\n // Build grid cells\n const cells: ReactElement[] = [];\n for (let row = 0; row < gridRows; row++) {\n for (let col = 0; col < gridColumns; col++) {\n const key = `${row},${col}`;\n const button = buttonAtCell.get(key);\n\n if (button) {\n const isDragging = dragState.draggingId === button.id;\n cells.push(\n <div\n key={key}\n style={{\n gridRow: `${row + 1} / span ${button.rowSpan}`,\n gridColumn: `${col + 1} / span ${button.colSpan}`,\n }}\n draggable={canUpdate}\n onDragStart={(e) => handleDragStart(e, button.id)}\n onDragEnd={handleDragEnd}\n className={`${isDragging ? 'opacity-40' : ''} ${canUpdate ? 'cursor-grab active:cursor-grabbing' : ''}`}\n >\n <VibeDeckButtonTile\n button={button}\n onExecute={onExecuteButton}\n onEdit={onEditButton}\n onDelete={onDeleteButton}\n canUpdate={canUpdate}\n canDelete={canDelete}\n canExecute={canExecute}\n executing={executingButtonId === button.id}\n previewMode={previewMode}\n />\n </div>\n );\n } else if (!occupiedCells.has(key)) {\n const isDropTarget = dragState.draggingId && dragState.targetKey === key;\n cells.push(\n <div\n key={key}\n style={{ gridRow: row + 1, gridColumn: col + 1 }}\n onDragOver={(e) => handleDragOver(e, key, row, col)}\n onDragLeave={handleDragLeave}\n onDrop={(e) => handleDrop(e, row, col)}\n >\n {dragState.draggingId ? (\n <div\n className={`w-full h-full min-h-[80px] rounded-lg border-2 border-dashed flex items-center justify-center transition-all ${\n isDropTarget\n ? 'border-action-primary-bg bg-[var(--color-accent-soft)] text-text-primary'\n : 'border-border-default/30 text-text-tertiary/30'\n }`}\n >\n {isDropTarget && (\n <span className=\"text-xs font-medium\">\n {tr('vibeDeck.dropHere', 'Drop here')}\n </span>\n )}\n </div>\n ) : canCreate ? (\n <button\n type=\"button\"\n onClick={() => onAddButton(row, col)}\n className=\"w-full h-full min-h-[80px] rounded-lg border-2 border-dashed border-border-strong text-text-tertiary hover:border-border-focus hover:text-text-secondary flex items-center justify-center transition-all hover:bg-bg-sunken\"\n >\n <Plus size={18} />\n </button>\n ) : (\n <div className=\"w-full h-full min-h-[80px] rounded-lg border-2 border-dashed border-border-default\" />\n )}\n </div>\n );\n }\n }\n }\n\n return (\n <div\n className=\"grid gap-3\"\n style={{\n gridTemplateColumns: `repeat(${gridColumns}, 1fr)`,\n gridTemplateRows: `repeat(${gridRows}, auto)`,\n }}\n >\n {cells}\n </div>\n );\n};\n"],"mappings":"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"}
|
package/dist/pages/AgentsPage.js
CHANGED
|
@@ -487,7 +487,7 @@ function at() {
|
|
|
487
487
|
onClick: () => {
|
|
488
488
|
gt("grid"), R(/* @__PURE__ */ new Set());
|
|
489
489
|
},
|
|
490
|
-
className: `flex items-center justify-center p-1.5 sm:p-2 max-md:min-h-11 max-md:min-w-11 rounded focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus ${T === "grid" ? "bg-[var(--color-accent-soft)] text-
|
|
490
|
+
className: `flex items-center justify-center p-1.5 sm:p-2 max-md:min-h-11 max-md:min-w-11 rounded focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus ${T === "grid" ? "bg-[var(--color-accent-soft)] text-text-primary" : "text-text-secondary hover:bg-bg-surface/60"}`,
|
|
491
491
|
"aria-label": b("agentsPage.gridViewTitle", "Grid view"),
|
|
492
492
|
"aria-pressed": T === "grid",
|
|
493
493
|
title: b("agentsPage.gridViewTitle", "Grid view"),
|
|
@@ -500,7 +500,7 @@ function at() {
|
|
|
500
500
|
onClick: () => {
|
|
501
501
|
gt("list"), R(/* @__PURE__ */ new Set());
|
|
502
502
|
},
|
|
503
|
-
className: `flex items-center justify-center p-1.5 sm:p-2 max-md:min-h-11 max-md:min-w-11 rounded focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus ${T === "list" ? "bg-[var(--color-accent-soft)] text-
|
|
503
|
+
className: `flex items-center justify-center p-1.5 sm:p-2 max-md:min-h-11 max-md:min-w-11 rounded focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus ${T === "list" ? "bg-[var(--color-accent-soft)] text-text-primary" : "text-text-secondary hover:bg-bg-surface/60"}`,
|
|
504
504
|
"aria-label": b("agentsPage.listViewTitle", "List view"),
|
|
505
505
|
"aria-pressed": T === "list",
|
|
506
506
|
title: b("agentsPage.listViewTitle", "List view"),
|