@burdenoff/microfe-vibecontrols 2026.528.7 → 2026.528.8
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/VibeControlsRoot.js +1 -0
- package/dist/VibeControlsRoot.js.map +1 -1
- package/dist/components/audit/AuditLogFilters.js +71 -47
- package/dist/components/audit/AuditLogFilters.js.map +1 -1
- package/dist/components/shared/AgentSetupBanner.js +50 -45
- package/dist/components/shared/AgentSetupBanner.js.map +1 -1
- package/dist/components/shared/LinkToVibePanel.js +97 -0
- package/dist/components/shared/LinkToVibePanel.js.map +1 -0
- package/dist/components/shared/index.js +1 -0
- package/dist/components/vibe/VibeLinkedEntitiesPanel.js +60 -0
- package/dist/components/vibe/VibeLinkedEntitiesPanel.js.map +1 -0
- package/dist/components/vibedeck/VibeDeckButtonTile.js +25 -23
- package/dist/components/vibedeck/VibeDeckButtonTile.js.map +1 -1
- package/dist/components/vibedeck/VibeDeckGrid.js +2 -2
- package/dist/components/vibedeck/VibeDeckGrid.js.map +1 -1
- package/dist/microfe-vibecontrols.css +1 -1
- package/dist/pages/AgentDetailsPage.js +2 -2
- package/dist/pages/AgentDetailsPage.js.map +1 -1
- package/dist/pages/AnalyticsPage.js +255 -204
- package/dist/pages/AnalyticsPage.js.map +1 -1
- package/dist/pages/OverviewPage.js +1 -11
- package/dist/pages/OverviewPage.js.map +1 -1
- package/dist/pages/SessionsPage.js +825 -629
- package/dist/pages/SessionsPage.js.map +1 -1
- package/dist/pages/VibeAuditPage.js +184 -153
- package/dist/pages/VibeAuditPage.js.map +1 -1
- package/dist/pages/VibeDeckPage.js +416 -379
- package/dist/pages/VibeDeckPage.js.map +1 -1
- package/dist/pages/VibeDetailsPage.js +621 -557
- package/dist/pages/VibeDetailsPage.js.map +1 -1
- package/dist/pages/tunnels/TunnelsTab.js +115 -90
- package/dist/pages/tunnels/TunnelsTab.js.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AgentSetupBanner.js","names":[],"sources":["../../../src/components/shared/AgentSetupBanner.tsx"],"sourcesContent":["import { useState, useCallback, useMemo } from 'react';\nimport { motion, AnimatePresence } from 'framer-motion';\nimport { Download, Copy, Check, ExternalLink, X, Terminal } from 'lucide-react';\nimport { useVibeNavigate } from '@/providers/VibeControlsProvider';\nimport { getProfileScopedKey } from '@burdenoff/fe-libs/shared/utils';\nimport { useTr } from '../../shared/hooks/useTr';\nimport { InstallTabs } from './InstallTabs';\n\ninterface AgentSetupBannerProps {\n /** Compact mode shows a single-line banner; full mode shows a multi-step card */\n variant?: 'compact' | 'full';\n /** Allow the banner to be dismissed */\n dismissible?: boolean;\n /** Storage key for dismissed state */\n dismissKey?: string;\n /** Additional CSS class names */\n className?: string;\n}\n\nconst NPM_URL = 'https://www.npmjs.com/package/@vibecontrols/agent';\n\n/**\n * Build the quick-install one-liner using the live host so the command shown\n * to the user reflects the environment they are actually on (local dev,\n * alpha, prod). InstallTabs uses the same `resolveHost()` shape — keep them\n * aligned by reading from `window.__VIBECONTROLS_HOST__` first, then\n * falling back to `window.location.host`.\n */\nfunction getQuickInstallCommand(): string {\n if (typeof window === 'undefined') {\n return 'curl -fsSL https://app.vibecontrols.com/install | bash';\n }\n const injected = (window as { __VIBECONTROLS_HOST__?: string }).__VIBECONTROLS_HOST__;\n const host = injected ?? window.location.host;\n return `curl -fsSL https://${host}/install | bash`;\n}\n\n/**\n * Reusable banner/card prompting users to install the VibeControls Agent CLI.\n * Used across multiple pages (Overview, Agents, Sessions, Tunnels, SSH, etc.)\n *\n * The full variant mirrors the Getting Started page's tabbed install widget\n * (`InstallTabs`) so users see a single consistent install flow wherever\n * the banner shows up.\n */\nexport function AgentSetupBanner({\n variant = 'compact',\n dismissible = true,\n dismissKey = 'vibecontrols-setup-banner-dismissed',\n className = '',\n}: AgentSetupBannerProps) {\n const tr = useTr();\n const handleNavigate = useVibeNavigate();\n const quickInstall = useMemo(() => getQuickInstallCommand(), []);\n const [copied, setCopied] = useState(false);\n const [dismissed, setDismissed] = useState(() => {\n try {\n return localStorage.getItem(getProfileScopedKey(dismissKey)) === 'true';\n } catch {\n return false;\n }\n });\n\n const handleCopy = useCallback(async (text: string) => {\n try {\n await navigator.clipboard.writeText(text);\n setCopied(true);\n setTimeout(() => setCopied(false), 2000);\n } catch {\n const el = document.createElement('textarea');\n el.value = text;\n document.body.appendChild(el);\n el.select();\n document.execCommand('copy');\n document.body.removeChild(el);\n setCopied(true);\n setTimeout(() => setCopied(false), 2000);\n }\n }, []);\n\n const handleDismiss = useCallback(() => {\n setDismissed(true);\n try {\n localStorage.setItem(getProfileScopedKey(dismissKey), 'true');\n } catch {\n // ignore\n }\n }, [dismissKey]);\n\n const bannerTransition = { duration: 0.3, ease: [0.4, 0, 0.2, 1] as const };\n\n // ─── Compact variant: single-line info banner ─────────────────────────\n if (variant === 'compact') {\n return (\n <AnimatePresence>\n {!dismissed && (\n <motion.div\n initial={{ opacity: 0, y: -8, height: 0 }}\n animate={{ opacity: 1, y: 0, height: 'auto' }}\n exit={{ opacity: 0, y: -8, height: 0 }}\n transition={bannerTransition}\n className={`relative flex flex-col items-start gap-3 px-4 py-3 bg-accent-purple-subtle border border-accent-purple/20 rounded-lg sm:flex-row sm:items-center ${className}`}\n >\n <Download className=\"size-5 text-accent-purple flex-shrink-0\" />\n <div className=\"flex min-w-0 flex-1 flex-wrap items-center gap-x-3 gap-y-1\">\n <span className=\"text-sm font-medium text-text-primary\">\n {tr(\n 'vibecontrols.shared.agentSetupBanner.installPrompt',\n 'Install the VibeControls Agent CLI to get started'\n )}\n </span>\n <code\n className=\"inline-flex min-w-0 max-w-full items-center gap-1.5 rounded border border-border-default bg-bg-sunken px-2.5 py-1 font-mono text-xs text-text-primary transition-colors hover:bg-bg-elevated\"\n onClick={() => handleCopy(quickInstall)}\n title={tr('vibecontrols.shared.agentSetupBanner.clickToCopy', 'Click to copy')}\n >\n <Terminal className=\"size-3\" />\n <span className=\"min-w-0 break-all\">{quickInstall}</span>\n {copied ? (\n <Check className=\"size-3 text-status-success-text\" />\n ) : (\n <Copy className=\"size-3 text-text-muted\" />\n )}\n </code>\n <button\n type=\"button\"\n onClick={() => handleNavigate('/getting-started')}\n className=\"text-sm text-accent-purple hover:underline flex items-center gap-1\"\n >\n {tr('vibecontrols.shared.agentSetupBanner.setupGuide', 'Setup guide')}{' '}\n <ExternalLink className=\"size-3\" />\n </button>\n </div>\n {dismissible && (\n <button\n type=\"button\"\n onClick={handleDismiss}\n className=\"flex-shrink-0 p-1 text-text-muted hover:text-text-secondary rounded transition-colors\"\n title={tr('vibecontrols.shared.agentSetupBanner.dismiss', 'Dismiss')}\n >\n <X className=\"size-4\" />\n </button>\n )}\n </motion.div>\n )}\n </AnimatePresence>\n );\n }\n\n // ─── Full variant: tabbed install card (mirrors Getting Started) ──────\n return (\n <AnimatePresence>\n {!dismissed && (\n <motion.div\n initial={{ opacity: 0, y: -8, height: 0 }}\n animate={{ opacity: 1, y: 0, height: 'auto' }}\n exit={{ opacity: 0, y: -8, height: 0 }}\n transition={bannerTransition}\n className={`relative border border-accent-purple/20 bg-accent-purple-subtle rounded-lg overflow-hidden ${className}`}\n >\n {dismissible && (\n <button\n type=\"button\"\n onClick={handleDismiss}\n className=\"absolute top-3 right-3 p-1 text-text-muted hover:text-text-secondary rounded transition-colors z-10\"\n title={tr('vibecontrols.shared.agentSetupBanner.dismiss', 'Dismiss')}\n >\n <X className=\"size-4\" />\n </button>\n )}\n\n <div className=\"p-5 sm:p-6\">\n <div className=\"flex items-start gap-3 mb-4\">\n <div className=\"flex-shrink-0 size-10 rounded-lg bg-accent-purple/10 flex items-center justify-center\">\n <Download className=\"size-5 text-accent-purple\" />\n </div>\n <div>\n <h3 className=\"text-base font-semibold text-text-primary\">\n {tr(\n 'vibecontrols.shared.agentSetupBanner.title',\n 'Get Started with VibeControls Agent'\n )}\n </h3>\n <p className=\"text-sm text-text-secondary mt-0.5\">\n {tr(\n 'vibecontrols.shared.agentSetupBanner.description',\n 'Install the CLI on any machine you want to manage. The only prerequisite is bun — the curl script installs it for you.'\n )}\n </p>\n </div>\n </div>\n\n {/* Tabbed install widget mirrors the Getting Started page */}\n <div className=\"ml-0 sm:ml-13\">\n <InstallTabs compact />\n </div>\n\n {/* Actions */}\n <div className=\"flex flex-wrap items-center gap-3 mt-5 ml-0 sm:ml-13\">\n <button\n type=\"button\"\n onClick={() => handleNavigate('/getting-started')}\n className=\"inline-flex items-center gap-2 px-4 py-2 text-sm bg-accent-purple text-text-inverse rounded-md hover:bg-accent-purple/90 transition-colors\"\n >\n {tr(\n 'vibecontrols.shared.agentSetupBanner.viewFullSetupGuide',\n 'View full setup guide'\n )}\n <ExternalLink className=\"size-3.5\" />\n </button>\n <a\n href={NPM_URL}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className=\"inline-flex items-center gap-2 px-4 py-2 text-sm border border-border-default text-text-primary rounded-md hover:bg-bg-sunken transition-colors\"\n >\n {tr('vibecontrols.shared.agentSetupBanner.npmPackage', 'npm package')}\n <ExternalLink className=\"size-3.5\" />\n </a>\n </div>\n </div>\n </motion.div>\n )}\n </AnimatePresence>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"AgentSetupBanner.js","names":[],"sources":["../../../src/components/shared/AgentSetupBanner.tsx"],"sourcesContent":["import { useState, useCallback, useMemo } from 'react';\nimport { motion, AnimatePresence } from 'framer-motion';\nimport { Download, Copy, Check, ExternalLink, X, Terminal } from 'lucide-react';\nimport { Button } from '@burdenoff/fe-libs/ui';\nimport { useVibeNavigate } from '@/providers/VibeControlsProvider';\nimport { getProfileScopedKey } from '@burdenoff/fe-libs/shared/utils';\nimport { useTr } from '../../shared/hooks/useTr';\nimport { InstallTabs } from './InstallTabs';\n\ninterface AgentSetupBannerProps {\n /** Compact mode shows a single-line banner; full mode shows a multi-step card */\n variant?: 'compact' | 'full';\n /** Allow the banner to be dismissed */\n dismissible?: boolean;\n /** Storage key for dismissed state */\n dismissKey?: string;\n /** Additional CSS class names */\n className?: string;\n}\n\nconst NPM_URL = 'https://www.npmjs.com/package/@vibecontrols/agent';\n\n/**\n * Build the quick-install one-liner using the live host so the command shown\n * to the user reflects the environment they are actually on (local dev,\n * alpha, prod). InstallTabs uses the same `resolveHost()` shape — keep them\n * aligned by reading from `window.__VIBECONTROLS_HOST__` first, then\n * falling back to `window.location.host`.\n */\nfunction getQuickInstallCommand(): string {\n if (typeof window === 'undefined') {\n return 'curl -fsSL https://app.vibecontrols.com/install | bash';\n }\n const injected = (window as { __VIBECONTROLS_HOST__?: string }).__VIBECONTROLS_HOST__;\n const host = injected ?? window.location.host;\n return `curl -fsSL https://${host}/install | bash`;\n}\n\n/**\n * Reusable banner/card prompting users to install the VibeControls Agent CLI.\n * Used across multiple pages (Overview, Agents, Sessions, Tunnels, SSH, etc.)\n *\n * The full variant mirrors the Getting Started page's tabbed install widget\n * (`InstallTabs`) so users see a single consistent install flow wherever\n * the banner shows up.\n */\nexport function AgentSetupBanner({\n variant = 'compact',\n dismissible = true,\n dismissKey = 'vibecontrols-setup-banner-dismissed',\n className = '',\n}: AgentSetupBannerProps) {\n const tr = useTr();\n const handleNavigate = useVibeNavigate();\n const quickInstall = useMemo(() => getQuickInstallCommand(), []);\n const [copied, setCopied] = useState(false);\n const [dismissed, setDismissed] = useState(() => {\n try {\n return localStorage.getItem(getProfileScopedKey(dismissKey)) === 'true';\n } catch {\n return false;\n }\n });\n\n const handleCopy = useCallback(async (text: string) => {\n try {\n await navigator.clipboard.writeText(text);\n setCopied(true);\n setTimeout(() => setCopied(false), 2000);\n } catch {\n const el = document.createElement('textarea');\n el.value = text;\n document.body.appendChild(el);\n el.select();\n document.execCommand('copy');\n document.body.removeChild(el);\n setCopied(true);\n setTimeout(() => setCopied(false), 2000);\n }\n }, []);\n\n const handleDismiss = useCallback(() => {\n setDismissed(true);\n try {\n localStorage.setItem(getProfileScopedKey(dismissKey), 'true');\n } catch {\n // ignore\n }\n }, [dismissKey]);\n\n const bannerTransition = { duration: 0.3, ease: [0.4, 0, 0.2, 1] as const };\n\n // ─── Compact variant: single-line info banner ─────────────────────────\n if (variant === 'compact') {\n return (\n <AnimatePresence>\n {!dismissed && (\n <motion.div\n initial={{ opacity: 0, y: -8, height: 0 }}\n animate={{ opacity: 1, y: 0, height: 'auto' }}\n exit={{ opacity: 0, y: -8, height: 0 }}\n transition={bannerTransition}\n className={`relative flex flex-col items-start gap-3 px-4 py-3 bg-accent-purple-subtle border border-accent-purple/20 rounded-lg sm:flex-row sm:items-center ${className}`}\n >\n <Download className=\"size-5 text-accent-purple flex-shrink-0\" />\n <div className=\"flex min-w-0 flex-1 flex-wrap items-center gap-x-3 gap-y-1\">\n <span className=\"text-sm font-medium text-text-primary\">\n {tr(\n 'vibecontrols.shared.agentSetupBanner.installPrompt',\n 'Install the VibeControls Agent CLI to get started'\n )}\n </span>\n <code\n className=\"inline-flex min-w-0 max-w-full items-center gap-1.5 rounded border border-border-default bg-bg-sunken px-2.5 py-1 font-mono text-xs text-text-primary transition-colors hover:bg-bg-elevated\"\n onClick={() => handleCopy(quickInstall)}\n title={tr('vibecontrols.shared.agentSetupBanner.clickToCopy', 'Click to copy')}\n >\n <Terminal className=\"size-3\" />\n <span className=\"min-w-0 break-all\">{quickInstall}</span>\n {copied ? (\n <Check className=\"size-3 text-status-success-text\" />\n ) : (\n <Copy className=\"size-3 text-text-muted\" />\n )}\n </code>\n <button\n type=\"button\"\n onClick={() => handleNavigate('/getting-started')}\n className=\"text-sm text-accent-purple hover:underline flex items-center gap-1\"\n >\n {tr('vibecontrols.shared.agentSetupBanner.setupGuide', 'Setup guide')}{' '}\n <ExternalLink className=\"size-3\" />\n </button>\n </div>\n {dismissible && (\n <button\n type=\"button\"\n onClick={handleDismiss}\n className=\"flex-shrink-0 p-1 text-text-muted hover:text-text-secondary rounded transition-colors\"\n title={tr('vibecontrols.shared.agentSetupBanner.dismiss', 'Dismiss')}\n >\n <X className=\"size-4\" />\n </button>\n )}\n </motion.div>\n )}\n </AnimatePresence>\n );\n }\n\n // ─── Full variant: tabbed install card (mirrors Getting Started) ──────\n return (\n <AnimatePresence>\n {!dismissed && (\n <motion.div\n initial={{ opacity: 0, y: -8, height: 0 }}\n animate={{ opacity: 1, y: 0, height: 'auto' }}\n exit={{ opacity: 0, y: -8, height: 0 }}\n transition={bannerTransition}\n className={`relative border border-accent-purple/20 bg-accent-purple-subtle rounded-lg overflow-hidden ${className}`}\n >\n {dismissible && (\n <button\n type=\"button\"\n onClick={handleDismiss}\n className=\"absolute top-3 right-3 p-1 text-text-muted hover:text-text-secondary rounded transition-colors z-10\"\n title={tr('vibecontrols.shared.agentSetupBanner.dismiss', 'Dismiss')}\n >\n <X className=\"size-4\" />\n </button>\n )}\n\n <div className=\"p-5 sm:p-6\">\n <div className=\"flex items-start gap-3 mb-4\">\n <div className=\"flex-shrink-0 size-10 rounded-lg bg-accent-purple/10 flex items-center justify-center\">\n <Download className=\"size-5 text-accent-purple\" />\n </div>\n <div>\n <h3 className=\"text-base font-semibold text-text-primary\">\n {tr(\n 'vibecontrols.shared.agentSetupBanner.title',\n 'Get Started with VibeControls Agent'\n )}\n </h3>\n <p className=\"text-sm text-text-secondary mt-0.5\">\n {tr(\n 'vibecontrols.shared.agentSetupBanner.description',\n 'Install the CLI on any machine you want to manage. The only prerequisite is bun — the curl script installs it for you.'\n )}\n </p>\n </div>\n </div>\n\n {/* Tabbed install widget mirrors the Getting Started page */}\n <div className=\"ml-0 sm:ml-13\">\n <InstallTabs compact />\n </div>\n\n {/* Actions */}\n <div className=\"flex flex-wrap items-center gap-3 mt-5 ml-0 sm:ml-13\">\n <Button\n variant=\"default\"\n onClick={() => handleNavigate('/getting-started')}\n className=\"gap-2\"\n >\n {tr(\n 'vibecontrols.shared.agentSetupBanner.viewFullSetupGuide',\n 'View full setup guide'\n )}\n <ExternalLink className=\"size-3.5\" />\n </Button>\n <Button variant=\"outline\" asChild className=\"gap-2\">\n <a href={NPM_URL} target=\"_blank\" rel=\"noopener noreferrer\">\n {tr('vibecontrols.shared.agentSetupBanner.npmPackage', 'npm package')}\n <ExternalLink className=\"size-3.5\" />\n </a>\n </Button>\n </div>\n </div>\n </motion.div>\n )}\n </AnimatePresence>\n );\n}\n"],"mappings":";;;;;;;;;;AAoBA,IAAM,IAAU;AAShB,SAAS,IAAiC;AAMxC,QALI,OAAO,SAAW,MACb,2DAIF,sBAFW,OAA8C,yBACvC,OAAO,SAAS,KACP;;AAWpC,SAAgB,EAAiB,EAC/B,aAAU,WACV,iBAAc,IACd,gBAAa,uCACb,eAAY,MACY;CACxB,IAAM,IAAK,GAAO,EACZ,IAAiB,GAAiB,EAClC,IAAe,QAAc,GAAwB,EAAE,EAAE,CAAC,EAC1D,CAAC,GAAQ,KAAa,EAAS,GAAM,EACrC,CAAC,GAAW,KAAgB,QAAe;AAC/C,MAAI;AACF,UAAO,aAAa,QAAQ,EAAoB,EAAW,CAAC,KAAK;UAC3D;AACN,UAAO;;GAET,EAEI,IAAa,EAAY,OAAO,MAAiB;AACrD,MAAI;AAGF,GAFA,MAAM,UAAU,UAAU,UAAU,EAAK,EACzC,EAAU,GAAK,EACf,iBAAiB,EAAU,GAAM,EAAE,IAAK;UAClC;GACN,IAAM,IAAK,SAAS,cAAc,WAAW;AAO7C,GANA,EAAG,QAAQ,GACX,SAAS,KAAK,YAAY,EAAG,EAC7B,EAAG,QAAQ,EACX,SAAS,YAAY,OAAO,EAC5B,SAAS,KAAK,YAAY,EAAG,EAC7B,EAAU,GAAK,EACf,iBAAiB,EAAU,GAAM,EAAE,IAAK;;IAEzC,EAAE,CAAC,EAEA,IAAgB,QAAkB;AACtC,IAAa,GAAK;AAClB,MAAI;AACF,gBAAa,QAAQ,EAAoB,EAAW,EAAE,OAAO;UACvD;IAGP,CAAC,EAAW,CAAC,EAEV,IAAmB;EAAE,UAAU;EAAK,MAAM;GAAC;GAAK;GAAG;GAAK;GAAE;EAAW;AA6D3E,QA1DI,MAAY,YAEZ,kBAAC,GAAD,EAAA,UACG,CAAC,KACA,kBAAC,EAAO,KAAR;EACE,SAAS;GAAE,SAAS;GAAG,GAAG;GAAI,QAAQ;GAAG;EACzC,SAAS;GAAE,SAAS;GAAG,GAAG;GAAG,QAAQ;GAAQ;EAC7C,MAAM;GAAE,SAAS;GAAG,GAAG;GAAI,QAAQ;GAAG;EACtC,YAAY;EACZ,WAAW,oJAAoJ;YALjK;GAOE,kBAAC,GAAD,EAAU,WAAU,2CAA4C,CAAA;GAChE,kBAAC,OAAD;IAAK,WAAU;cAAf;KACE,kBAAC,QAAD;MAAM,WAAU;gBACb,EACC,sDACA,oDACD;MACI,CAAA;KACP,kBAAC,QAAD;MACE,WAAU;MACV,eAAe,EAAW,EAAa;MACvC,OAAO,EAAG,oDAAoD,gBAAgB;gBAHhF;OAKE,kBAAC,GAAD,EAAU,WAAU,UAAW,CAAA;OAC/B,kBAAC,QAAD;QAAM,WAAU;kBAAqB;QAAoB,CAAA;OACxD,IACC,kBAAC,GAAD,EAAO,WAAU,mCAAoC,CAAA,GAErD,kBAAC,GAAD,EAAM,WAAU,0BAA2B,CAAA;OAExC;;KACP,kBAAC,UAAD;MACE,MAAK;MACL,eAAe,EAAe,mBAAmB;MACjD,WAAU;gBAHZ;OAKG,EAAG,mDAAmD,cAAc;OAAE;OACvE,kBAAC,GAAD,EAAc,WAAU,UAAW,CAAA;OAC5B;;KACL;;GACL,KACC,kBAAC,UAAD;IACE,MAAK;IACL,SAAS;IACT,WAAU;IACV,OAAO,EAAG,gDAAgD,UAAU;cAEpE,kBAAC,GAAD,EAAG,WAAU,UAAW,CAAA;IACjB,CAAA;GAEA;KAEC,CAAA,GAMpB,kBAAC,GAAD,EAAA,UACG,CAAC,KACA,kBAAC,EAAO,KAAR;EACE,SAAS;GAAE,SAAS;GAAG,GAAG;GAAI,QAAQ;GAAG;EACzC,SAAS;GAAE,SAAS;GAAG,GAAG;GAAG,QAAQ;GAAQ;EAC7C,MAAM;GAAE,SAAS;GAAG,GAAG;GAAI,QAAQ;GAAG;EACtC,YAAY;EACZ,WAAW,8FAA8F;YAL3G,CAOG,KACC,kBAAC,UAAD;GACE,MAAK;GACL,SAAS;GACT,WAAU;GACV,OAAO,EAAG,gDAAgD,UAAU;aAEpE,kBAAC,GAAD,EAAG,WAAU,UAAW,CAAA;GACjB,CAAA,EAGX,kBAAC,OAAD;GAAK,WAAU;aAAf;IACE,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,OAAD;MAAK,WAAU;gBACb,kBAAC,GAAD,EAAU,WAAU,6BAA8B,CAAA;MAC9C,CAAA,EACN,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,MAAD;MAAI,WAAU;gBACX,EACC,8CACA,sCACD;MACE,CAAA,EACL,kBAAC,KAAD;MAAG,WAAU;gBACV,EACC,oDACA,yHACD;MACC,CAAA,CACA,EAAA,CAAA,CACF;;IAGN,kBAAC,OAAD;KAAK,WAAU;eACb,kBAAC,GAAD,EAAa,SAAA,IAAU,CAAA;KACnB,CAAA;IAGN,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,GAAD;MACE,SAAQ;MACR,eAAe,EAAe,mBAAmB;MACjD,WAAU;gBAHZ,CAKG,EACC,2DACA,wBACD,EACD,kBAAC,GAAD,EAAc,WAAU,YAAa,CAAA,CAC9B;SACT,kBAAC,GAAD;MAAQ,SAAQ;MAAU,SAAA;MAAQ,WAAU;gBAC1C,kBAAC,KAAD;OAAG,MAAM;OAAS,QAAO;OAAS,KAAI;iBAAtC,CACG,EAAG,mDAAmD,cAAc,EACrE,kBAAC,GAAD,EAAc,WAAU,YAAa,CAAA,CACnC;;MACG,CAAA,CACL;;IACF;KACK;KAEC,CAAA"}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { useTr as e } from "../../shared/hooks/useTr.js";
|
|
2
|
+
import { useListVibesQuery as t } from "../../generated/wspace-operations.js";
|
|
3
|
+
import { useMemo as n } from "react";
|
|
4
|
+
import { jsx as r, jsxs as i } from "react/jsx-runtime";
|
|
5
|
+
//#region src/components/shared/LinkToVibePanel.tsx
|
|
6
|
+
function a({ value: a, onChange: o, label: s, disabled: c = !1 }) {
|
|
7
|
+
let l = e(), { data: u, loading: d } = t({
|
|
8
|
+
variables: { pagination: { limit: 200 } },
|
|
9
|
+
fetchPolicy: "cache-first"
|
|
10
|
+
}), f = n(() => (u?.vibes?.items ?? []).map((e) => ({
|
|
11
|
+
id: e.id,
|
|
12
|
+
name: e.name
|
|
13
|
+
})), [u?.vibes?.items]), p = (e) => {
|
|
14
|
+
o({
|
|
15
|
+
vibeId: e,
|
|
16
|
+
visibility: e ? a.visibility : "PRIVATE"
|
|
17
|
+
});
|
|
18
|
+
}, m = (e) => {
|
|
19
|
+
o({
|
|
20
|
+
...a,
|
|
21
|
+
visibility: e
|
|
22
|
+
});
|
|
23
|
+
};
|
|
24
|
+
return /* @__PURE__ */ i("fieldset", {
|
|
25
|
+
disabled: c,
|
|
26
|
+
className: "space-y-3 rounded-md border border-border-default bg-bg-surface p-3",
|
|
27
|
+
children: [
|
|
28
|
+
/* @__PURE__ */ r("legend", {
|
|
29
|
+
className: "px-1 text-sm font-medium text-text-secondary",
|
|
30
|
+
children: s || l("vibecontrols.linkToVibe.title", "Link to Vibe (optional)")
|
|
31
|
+
}),
|
|
32
|
+
/* @__PURE__ */ r("p", {
|
|
33
|
+
className: "text-xs text-text-tertiary",
|
|
34
|
+
children: l("vibecontrols.linkToVibe.description", "Associate this entity with a vibe so it shows up under the vibe’s related tabs.")
|
|
35
|
+
}),
|
|
36
|
+
/* @__PURE__ */ i("select", {
|
|
37
|
+
value: a.vibeId ?? "",
|
|
38
|
+
onChange: (e) => p(e.target.value === "" ? null : e.target.value),
|
|
39
|
+
"aria-label": l("vibecontrols.linkToVibe.pickVibeAria", "Pick a vibe to link"),
|
|
40
|
+
className: "w-full rounded-md border border-border-default bg-bg-sunken px-3 py-2 text-sm text-text-primary focus:outline-none focus:ring-1 focus:ring-action-primary-bg",
|
|
41
|
+
children: [/* @__PURE__ */ r("option", {
|
|
42
|
+
value: "",
|
|
43
|
+
children: d ? l("vibecontrols.linkToVibe.loadingVibes", "Loading vibes…") : l("vibecontrols.linkToVibe.pickVibe", "Pick a vibe to link…")
|
|
44
|
+
}), f.map((e) => /* @__PURE__ */ r("option", {
|
|
45
|
+
value: e.id,
|
|
46
|
+
children: e.name
|
|
47
|
+
}, e.id))]
|
|
48
|
+
}),
|
|
49
|
+
a.vibeId && /* @__PURE__ */ i("div", {
|
|
50
|
+
className: "space-y-2",
|
|
51
|
+
children: [/* @__PURE__ */ r("p", {
|
|
52
|
+
className: "text-xs font-medium text-text-secondary",
|
|
53
|
+
children: l("vibecontrols.linkToVibe.visibilityLabel", "Who can see it")
|
|
54
|
+
}), /* @__PURE__ */ i("div", {
|
|
55
|
+
role: "radiogroup",
|
|
56
|
+
"aria-label": l("vibecontrols.linkToVibe.visibilityLabel", "Who can see it"),
|
|
57
|
+
className: "space-y-1.5",
|
|
58
|
+
children: [/* @__PURE__ */ i("label", {
|
|
59
|
+
className: "flex cursor-pointer items-start gap-2 text-sm",
|
|
60
|
+
children: [/* @__PURE__ */ r("input", {
|
|
61
|
+
type: "radio",
|
|
62
|
+
name: "link-to-vibe-visibility",
|
|
63
|
+
value: "PRIVATE",
|
|
64
|
+
checked: a.visibility === "PRIVATE",
|
|
65
|
+
onChange: () => m("PRIVATE"),
|
|
66
|
+
className: "mt-1"
|
|
67
|
+
}), /* @__PURE__ */ r("span", {
|
|
68
|
+
className: "text-text-primary",
|
|
69
|
+
children: l("vibecontrols.linkToVibe.private", "Private to me")
|
|
70
|
+
})]
|
|
71
|
+
}), /* @__PURE__ */ i("label", {
|
|
72
|
+
className: "flex cursor-pointer items-start gap-2 text-sm",
|
|
73
|
+
children: [/* @__PURE__ */ r("input", {
|
|
74
|
+
type: "radio",
|
|
75
|
+
name: "link-to-vibe-visibility",
|
|
76
|
+
value: "SHARED_WITH_VIBE",
|
|
77
|
+
checked: a.visibility === "SHARED_WITH_VIBE",
|
|
78
|
+
onChange: () => m("SHARED_WITH_VIBE"),
|
|
79
|
+
className: "mt-1"
|
|
80
|
+
}), /* @__PURE__ */ r("span", {
|
|
81
|
+
className: "text-text-primary",
|
|
82
|
+
children: l("vibecontrols.linkToVibe.shared", "Shared with everyone who has access to this vibe")
|
|
83
|
+
})]
|
|
84
|
+
})]
|
|
85
|
+
})]
|
|
86
|
+
})
|
|
87
|
+
]
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
var o = {
|
|
91
|
+
vibeId: null,
|
|
92
|
+
visibility: "PRIVATE"
|
|
93
|
+
};
|
|
94
|
+
//#endregion
|
|
95
|
+
export { o as LINK_TO_VIBE_DEFAULT, a as LinkToVibePanel };
|
|
96
|
+
|
|
97
|
+
//# sourceMappingURL=LinkToVibePanel.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"LinkToVibePanel.js","names":[],"sources":["../../../src/components/shared/LinkToVibePanel.tsx"],"sourcesContent":["import { useMemo } from 'react';\nimport { useListVibesQuery } from '@/generated/wspace-operations';\nimport { useTr } from '../../shared/hooks/useTr';\n\n/**\n * Visibility for an entity that links to a vibe.\n *\n * - `PRIVATE`: only the creator can see it.\n * - `SHARED_WITH_VIBE`: anyone with access to the linked vibe can see it.\n *\n * Mirrors the proposed `EntityVisibility` enum on the wspace-vibecontrols-svc\n * schema. If the schema does not yet define this enum, callers should drop\n * the field from their mutation input (see the TODO in each form).\n */\nexport type LinkToVibeVisibility = 'PRIVATE' | 'SHARED_WITH_VIBE';\n\nexport interface LinkToVibeValue {\n vibeId: string | null;\n visibility: LinkToVibeVisibility;\n}\n\ninterface LinkToVibePanelProps {\n value: LinkToVibeValue;\n onChange: (value: LinkToVibeValue) => void;\n /** Optional label override */\n label?: string;\n /** Disable the panel entirely (e.g. while submitting). */\n disabled?: boolean;\n}\n\n/**\n * Optional \"Link to Vibe\" + visibility selector reusable across every entity\n * create form (VibeDeck / Action / Tunnel / Sandbox / AiPrompt / ScheduledJob).\n *\n * Forms thread the value into their mutation input as `vibeId` + `visibility`.\n * If the underlying schema does not yet define either field, the form should\n * omit it from the mutation; the resolver will pick a sensible default.\n */\nexport function LinkToVibePanel({\n value,\n onChange,\n label,\n disabled = false,\n}: LinkToVibePanelProps) {\n const tr = useTr();\n const { data, loading } = useListVibesQuery({\n variables: { pagination: { limit: 200 } },\n fetchPolicy: 'cache-first',\n });\n\n const vibeOptions = useMemo(() => {\n const items = data?.vibes?.items ?? [];\n return items.map((v: { id: string; name: string }) => ({ id: v.id, name: v.name }));\n }, [data?.vibes?.items]);\n\n const handleVibeChange = (vibeId: string | null) => {\n onChange({ vibeId, visibility: vibeId ? value.visibility : 'PRIVATE' });\n };\n\n const handleVisibilityChange = (visibility: LinkToVibeVisibility) => {\n onChange({ ...value, visibility });\n };\n\n return (\n <fieldset\n disabled={disabled}\n className=\"space-y-3 rounded-md border border-border-default bg-bg-surface p-3\"\n >\n <legend className=\"px-1 text-sm font-medium text-text-secondary\">\n {label || tr('vibecontrols.linkToVibe.title', 'Link to Vibe (optional)')}\n </legend>\n <p className=\"text-xs text-text-tertiary\">\n {tr(\n 'vibecontrols.linkToVibe.description',\n 'Associate this entity with a vibe so it shows up under the vibe’s related tabs.'\n )}\n </p>\n\n <select\n value={value.vibeId ?? ''}\n onChange={(e) => handleVibeChange(e.target.value === '' ? null : e.target.value)}\n aria-label={tr('vibecontrols.linkToVibe.pickVibeAria', 'Pick a vibe to link')}\n className=\"w-full rounded-md border border-border-default bg-bg-sunken px-3 py-2 text-sm text-text-primary focus:outline-none focus:ring-1 focus:ring-action-primary-bg\"\n >\n <option value=\"\">\n {loading\n ? tr('vibecontrols.linkToVibe.loadingVibes', 'Loading vibes…')\n : tr('vibecontrols.linkToVibe.pickVibe', 'Pick a vibe to link…')}\n </option>\n {vibeOptions.map((opt: { id: string; name: string }) => (\n <option key={opt.id} value={opt.id}>\n {opt.name}\n </option>\n ))}\n </select>\n\n {value.vibeId && (\n <div className=\"space-y-2\">\n <p className=\"text-xs font-medium text-text-secondary\">\n {tr('vibecontrols.linkToVibe.visibilityLabel', 'Who can see it')}\n </p>\n <div\n role=\"radiogroup\"\n aria-label={tr('vibecontrols.linkToVibe.visibilityLabel', 'Who can see it')}\n className=\"space-y-1.5\"\n >\n <label className=\"flex cursor-pointer items-start gap-2 text-sm\">\n <input\n type=\"radio\"\n name=\"link-to-vibe-visibility\"\n value=\"PRIVATE\"\n checked={value.visibility === 'PRIVATE'}\n onChange={() => handleVisibilityChange('PRIVATE')}\n className=\"mt-1\"\n />\n <span className=\"text-text-primary\">\n {tr('vibecontrols.linkToVibe.private', 'Private to me')}\n </span>\n </label>\n <label className=\"flex cursor-pointer items-start gap-2 text-sm\">\n <input\n type=\"radio\"\n name=\"link-to-vibe-visibility\"\n value=\"SHARED_WITH_VIBE\"\n checked={value.visibility === 'SHARED_WITH_VIBE'}\n onChange={() => handleVisibilityChange('SHARED_WITH_VIBE')}\n className=\"mt-1\"\n />\n <span className=\"text-text-primary\">\n {tr(\n 'vibecontrols.linkToVibe.shared',\n 'Shared with everyone who has access to this vibe'\n )}\n </span>\n </label>\n </div>\n </div>\n )}\n </fieldset>\n );\n}\n\nexport const LINK_TO_VIBE_DEFAULT: LinkToVibeValue = {\n vibeId: null,\n visibility: 'PRIVATE',\n};\n"],"mappings":";;;;;AAsCA,SAAgB,EAAgB,EAC9B,UACA,aACA,UACA,cAAW,MACY;CACvB,IAAM,IAAK,GAAO,EACZ,EAAE,SAAM,eAAY,EAAkB;EAC1C,WAAW,EAAE,YAAY,EAAE,OAAO,KAAK,EAAE;EACzC,aAAa;EACd,CAAC,EAEI,IAAc,SACJ,GAAM,OAAO,SAAS,EAAE,EACzB,KAAK,OAAqC;EAAE,IAAI,EAAE;EAAI,MAAM,EAAE;EAAM,EAAE,EAClF,CAAC,GAAM,OAAO,MAAM,CAAC,EAElB,KAAoB,MAA0B;AAClD,IAAS;GAAE;GAAQ,YAAY,IAAS,EAAM,aAAa;GAAW,CAAC;IAGnE,KAA0B,MAAqC;AACnE,IAAS;GAAE,GAAG;GAAO;GAAY,CAAC;;AAGpC,QACE,kBAAC,YAAD;EACY;EACV,WAAU;YAFZ;GAIE,kBAAC,UAAD;IAAQ,WAAU;cACf,KAAS,EAAG,iCAAiC,0BAA0B;IACjE,CAAA;GACT,kBAAC,KAAD;IAAG,WAAU;cACV,EACC,uCACA,kFACD;IACC,CAAA;GAEJ,kBAAC,UAAD;IACE,OAAO,EAAM,UAAU;IACvB,WAAW,MAAM,EAAiB,EAAE,OAAO,UAAU,KAAK,OAAO,EAAE,OAAO,MAAM;IAChF,cAAY,EAAG,wCAAwC,sBAAsB;IAC7E,WAAU;cAJZ,CAME,kBAAC,UAAD;KAAQ,OAAM;eACX,IACG,EAAG,wCAAwC,iBAAiB,GAC5D,EAAG,oCAAoC,uBAAuB;KAC3D,CAAA,EACR,EAAY,KAAK,MAChB,kBAAC,UAAD;KAAqB,OAAO,EAAI;eAC7B,EAAI;KACE,EAFI,EAAI,GAER,CACT,CACK;;GAER,EAAM,UACL,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,KAAD;KAAG,WAAU;eACV,EAAG,2CAA2C,iBAAiB;KAC9D,CAAA,EACJ,kBAAC,OAAD;KACE,MAAK;KACL,cAAY,EAAG,2CAA2C,iBAAiB;KAC3E,WAAU;eAHZ,CAKE,kBAAC,SAAD;MAAO,WAAU;gBAAjB,CACE,kBAAC,SAAD;OACE,MAAK;OACL,MAAK;OACL,OAAM;OACN,SAAS,EAAM,eAAe;OAC9B,gBAAgB,EAAuB,UAAU;OACjD,WAAU;OACV,CAAA,EACF,kBAAC,QAAD;OAAM,WAAU;iBACb,EAAG,mCAAmC,gBAAgB;OAClD,CAAA,CACD;SACR,kBAAC,SAAD;MAAO,WAAU;gBAAjB,CACE,kBAAC,SAAD;OACE,MAAK;OACL,MAAK;OACL,OAAM;OACN,SAAS,EAAM,eAAe;OAC9B,gBAAgB,EAAuB,mBAAmB;OAC1D,WAAU;OACV,CAAA,EACF,kBAAC,QAAD;OAAM,WAAU;iBACb,EACC,kCACA,mDACD;OACI,CAAA,CACD;QACJ;OACF;;GAEC;;;AAIf,IAAa,IAAwC;CACnD,QAAQ;CACR,YAAY;CACb"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { useTr as e } from "../../shared/hooks/useTr.js";
|
|
2
|
+
import { EmptyState as t } from "../shared/EmptyState.js";
|
|
3
|
+
import "../shared/index.js";
|
|
4
|
+
import { useEffect as n } from "react";
|
|
5
|
+
import { Link2 as r, Plus as i } from "lucide-react";
|
|
6
|
+
import { jsx as a, jsxs as o } from "react/jsx-runtime";
|
|
7
|
+
//#region src/components/vibe/VibeLinkedEntitiesPanel.tsx
|
|
8
|
+
var s = {
|
|
9
|
+
vibedecks: {
|
|
10
|
+
singular: "Vibe Deck",
|
|
11
|
+
plural: "Vibe Decks"
|
|
12
|
+
},
|
|
13
|
+
actions: {
|
|
14
|
+
singular: "Action",
|
|
15
|
+
plural: "Actions"
|
|
16
|
+
},
|
|
17
|
+
scheduledJobs: {
|
|
18
|
+
singular: "Scheduled Job",
|
|
19
|
+
plural: "Scheduled Jobs"
|
|
20
|
+
},
|
|
21
|
+
sandboxes: {
|
|
22
|
+
singular: "Sandbox",
|
|
23
|
+
plural: "Sandboxes"
|
|
24
|
+
},
|
|
25
|
+
aiPrompts: {
|
|
26
|
+
singular: "AI Prompt",
|
|
27
|
+
plural: "AI Prompts"
|
|
28
|
+
},
|
|
29
|
+
tunnels: {
|
|
30
|
+
singular: "Tunnel",
|
|
31
|
+
plural: "Tunnels"
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
function c({ vibeId: c, kind: l }) {
|
|
35
|
+
let u = e(), d = s[l];
|
|
36
|
+
return n(() => {
|
|
37
|
+
console.warn(`[VibeLinkedEntitiesPanel] No Vibe.linkedEntities.${l} federation field yet; rendering placeholder for vibe ${c}.`);
|
|
38
|
+
}, [c, l]), /* @__PURE__ */ a("div", {
|
|
39
|
+
className: "rounded-lg border border-border-default bg-bg-surface p-6",
|
|
40
|
+
children: /* @__PURE__ */ a(t, {
|
|
41
|
+
icon: /* @__PURE__ */ a(r, {
|
|
42
|
+
size: 48,
|
|
43
|
+
className: "text-text-secondary"
|
|
44
|
+
}),
|
|
45
|
+
title: u(`vibeDetails.linked.${l}.title`, `${d.plural} linked to this vibe`),
|
|
46
|
+
description: u(`vibeDetails.linked.${l}.description`, `Linked ${d.plural.toLowerCase()} for this vibe will appear here. Coming soon.`),
|
|
47
|
+
action: /* @__PURE__ */ o("button", {
|
|
48
|
+
type: "button",
|
|
49
|
+
disabled: !0,
|
|
50
|
+
className: "inline-flex cursor-not-allowed items-center gap-2 rounded-md border border-border-default px-3 py-2 text-sm font-medium text-text-tertiary opacity-60",
|
|
51
|
+
title: u("vibeDetails.linked.comingSoon", "Coming soon"),
|
|
52
|
+
children: [/* @__PURE__ */ a(i, { size: 14 }), u("vibeDetails.linked.linkExisting", `Link existing ${d.singular}`)]
|
|
53
|
+
})
|
|
54
|
+
})
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
//#endregion
|
|
58
|
+
export { c as VibeLinkedEntitiesPanel };
|
|
59
|
+
|
|
60
|
+
//# sourceMappingURL=VibeLinkedEntitiesPanel.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"VibeLinkedEntitiesPanel.js","names":[],"sources":["../../../src/components/vibe/VibeLinkedEntitiesPanel.tsx"],"sourcesContent":["import { useEffect } from 'react';\nimport { Link2, Plus } from 'lucide-react';\nimport { EmptyState } from '@/components/shared';\nimport { useTr } from '@/shared/hooks/useTr';\n\nexport type LinkedEntityKind =\n | 'vibedecks'\n | 'actions'\n | 'scheduledJobs'\n | 'sandboxes'\n | 'aiPrompts'\n | 'tunnels';\n\ninterface VibeLinkedEntitiesPanelProps {\n vibeId: string;\n kind: LinkedEntityKind;\n}\n\nconst KIND_LABELS: Record<LinkedEntityKind, { singular: string; plural: string }> = {\n vibedecks: { singular: 'Vibe Deck', plural: 'Vibe Decks' },\n actions: { singular: 'Action', plural: 'Actions' },\n scheduledJobs: { singular: 'Scheduled Job', plural: 'Scheduled Jobs' },\n sandboxes: { singular: 'Sandbox', plural: 'Sandboxes' },\n aiPrompts: { singular: 'AI Prompt', plural: 'AI Prompts' },\n tunnels: { singular: 'Tunnel', plural: 'Tunnels' },\n};\n\n/**\n * F10 round-3 panel — shows entities linked to a vibe.\n *\n * Expected data source is `Vibe.linkedEntities.{kind}` federation field\n * on wspace-vibecontrols-svc. Until that ships, we render a \"Coming\n * soon\" empty state. Once it does, swap the placeholder for a list\n * query and add the \"+ Link existing\" picker.\n *\n * TODO(boff-vibe-linked-entities): wire the real query + picker.\n */\nexport function VibeLinkedEntitiesPanel({ vibeId, kind }: VibeLinkedEntitiesPanelProps) {\n const tr = useTr();\n const labels = KIND_LABELS[kind];\n\n useEffect(() => {\n // Single console warn per mount so dev sees the placeholder reason.\n console.warn(\n `[VibeLinkedEntitiesPanel] No Vibe.linkedEntities.${kind} federation field yet; rendering placeholder for vibe ${vibeId}.`\n );\n }, [vibeId, kind]);\n\n return (\n <div className=\"rounded-lg border border-border-default bg-bg-surface p-6\">\n <EmptyState\n icon={<Link2 size={48} className=\"text-text-secondary\" />}\n title={tr(`vibeDetails.linked.${kind}.title`, `${labels.plural} linked to this vibe`)}\n description={tr(\n `vibeDetails.linked.${kind}.description`,\n `Linked ${labels.plural.toLowerCase()} for this vibe will appear here. Coming soon.`\n )}\n action={\n <button\n type=\"button\"\n disabled\n className=\"inline-flex cursor-not-allowed items-center gap-2 rounded-md border border-border-default px-3 py-2 text-sm font-medium text-text-tertiary opacity-60\"\n title={tr('vibeDetails.linked.comingSoon', 'Coming soon')}\n >\n <Plus size={14} />\n {tr('vibeDetails.linked.linkExisting', `Link existing ${labels.singular}`)}\n </button>\n }\n />\n </div>\n );\n}\n"],"mappings":";;;;;;;AAkBA,IAAM,IAA8E;CAClF,WAAW;EAAE,UAAU;EAAa,QAAQ;EAAc;CAC1D,SAAS;EAAE,UAAU;EAAU,QAAQ;EAAW;CAClD,eAAe;EAAE,UAAU;EAAiB,QAAQ;EAAkB;CACtE,WAAW;EAAE,UAAU;EAAW,QAAQ;EAAa;CACvD,WAAW;EAAE,UAAU;EAAa,QAAQ;EAAc;CAC1D,SAAS;EAAE,UAAU;EAAU,QAAQ;EAAW;CACnD;AAYD,SAAgB,EAAwB,EAAE,WAAQ,WAAsC;CACtF,IAAM,IAAK,GAAO,EACZ,IAAS,EAAY;AAS3B,QAPA,QAAgB;AAEd,UAAQ,KACN,oDAAoD,EAAK,wDAAwD,EAAO,GACzH;IACA,CAAC,GAAQ,EAAK,CAAC,EAGhB,kBAAC,OAAD;EAAK,WAAU;YACb,kBAAC,GAAD;GACE,MAAM,kBAAC,GAAD;IAAO,MAAM;IAAI,WAAU;IAAwB,CAAA;GACzD,OAAO,EAAG,sBAAsB,EAAK,SAAS,GAAG,EAAO,OAAO,sBAAsB;GACrF,aAAa,EACX,sBAAsB,EAAK,eAC3B,UAAU,EAAO,OAAO,aAAa,CAAC,+CACvC;GACD,QACE,kBAAC,UAAD;IACE,MAAK;IACL,UAAA;IACA,WAAU;IACV,OAAO,EAAG,iCAAiC,cAAc;cAJ3D,CAME,kBAAC,GAAD,EAAM,MAAM,IAAM,CAAA,EACjB,EAAG,mCAAmC,iBAAiB,EAAO,WAAW,CACnE;;GAEX,CAAA;EACE,CAAA"}
|
|
@@ -22,35 +22,30 @@ var h = ({ button: h, onExecute: g, onEdit: _, onDelete: v, canUpdate: y, canDel
|
|
|
22
22
|
orange: "border-status-warning-border/40 bg-gradient-to-br from-status-warning-bg/15 to-status-warning-bg/5 hover:shadow-status-warning-bg/20",
|
|
23
23
|
cyan: "border-accent-teal/40 bg-gradient-to-br from-accent-teal/15 to-accent-teal/5 hover:shadow-accent-teal/20",
|
|
24
24
|
pink: "border-accent-purple/40 bg-gradient-to-br from-accent-purple/15 to-accent-purple/5 hover:shadow-accent-purple/20"
|
|
25
|
-
}, A = D ? h.color && k[h.color] ? k[h.color]
|
|
25
|
+
}, A = "border-border-default bg-bg-elevated/80 backdrop-blur-md shadow-lg hover:border-border-focus hover:shadow-xl", j = D ? h.color && k[h.color] ? `${k[h.color]} backdrop-blur-md shadow-lg hover:shadow-xl` : A : h.color && O[h.color] ? `${O[h.color]} backdrop-blur-md shadow-md hover:shadow-lg` : A, M = h.action?.actionType || h.actionType, N = h.action?.command ?? h.command, P = h.action?.script ?? null, F = h.action?.webhookUrl ?? h.webhookUrl, I = h.action?.displayName || h.action?.name || null, L = M === "WEBHOOK", R = M === "SCRIPT", z = L ? i : R ? r : c, B = L ? `${h.webhookMethod || "POST"} ${F || ""}` : R ? `Script (${h.scriptInterpreter || "bash"})${P ? `: ${P.slice(0, 60)}` : ""}` : `${N || ""}${h.keyboardShortcut ? ` (${h.keyboardShortcut})` : ""}`, V = () => {
|
|
26
26
|
D && x && !S && g(h.id);
|
|
27
|
-
},
|
|
27
|
+
}, H = C === "executing" ? "ring-2 ring-action-primary-bg/60 ring-offset-1" : C === "success" ? "ring-2 ring-status-success-border/50 ring-offset-1" : C === "error" ? "ring-2 ring-status-error-border/50 ring-offset-1" : "", U = C === "executing" ? "border-action-primary-bg/60" : C === "success" ? "border-status-success-border/50" : C === "error" ? "border-status-error-border/50" : "", W = D ? x && !S : y;
|
|
28
28
|
return /* @__PURE__ */ m(f.div, {
|
|
29
|
-
className: `relative group h-full ${
|
|
30
|
-
whileHover: {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
},
|
|
34
|
-
whileTap: D ? {
|
|
35
|
-
scale: .95,
|
|
36
|
-
transition: { duration: .1 }
|
|
37
|
-
} : { scale: .96 },
|
|
29
|
+
className: `relative group h-full ${H} ${w ? "ring-2 ring-action-primary-bg ring-offset-1" : ""} ${T ? "ring-2 ring-action-primary-bg/40 ring-offset-1" : ""} ${W ? "cursor-pointer" : ""} rounded-lg focus-visible:ring-2 focus-visible:ring-border-focus focus-visible:outline-none`,
|
|
30
|
+
whileHover: W ? { scale: 1.03 } : void 0,
|
|
31
|
+
whileTap: W ? { scale: .97 } : void 0,
|
|
32
|
+
transition: { duration: .15 },
|
|
38
33
|
children: [
|
|
39
34
|
/* @__PURE__ */ m("div", {
|
|
40
35
|
role: "button",
|
|
41
36
|
tabIndex: 0,
|
|
42
37
|
onKeyDown: (e) => {
|
|
43
|
-
(e.key === "Enter" || e.key === " ") && (e.preventDefault(),
|
|
38
|
+
(e.key === "Enter" || e.key === " ") && (e.preventDefault(), V());
|
|
44
39
|
},
|
|
45
|
-
onClick:
|
|
46
|
-
className: `w-full h-full min-h-[80px] rounded-lg border-2 p-3 flex flex-col items-center justify-center gap-2 transition-all ${
|
|
47
|
-
title:
|
|
40
|
+
onClick: V,
|
|
41
|
+
className: `w-full h-full min-h-[80px] rounded-lg border-2 p-3 flex flex-col items-center justify-center gap-2 transition-all duration-200 ${j} ${U} ${C === "executing" ? "animate-pulse" : ""}`,
|
|
42
|
+
title: B,
|
|
48
43
|
children: [
|
|
49
|
-
|
|
44
|
+
L && h.webhookMethod && /* @__PURE__ */ p("span", {
|
|
50
45
|
className: `absolute left-1.5 text-[9px] font-bold text-text-tertiary bg-bg-tertiary/80 rounded px-1 py-0.5 uppercase ${!D && E ? "top-8" : "top-1.5"}`,
|
|
51
46
|
children: h.webhookMethod
|
|
52
47
|
}),
|
|
53
|
-
|
|
48
|
+
R && h.scriptInterpreter && /* @__PURE__ */ p("span", {
|
|
54
49
|
className: `absolute left-1.5 text-[9px] font-bold text-text-tertiary bg-bg-tertiary/80 rounded px-1 py-0.5 ${!D && E ? "top-8" : "top-1.5"}`,
|
|
55
50
|
children: h.scriptInterpreter
|
|
56
51
|
}),
|
|
@@ -67,7 +62,7 @@ var h = ({ button: h, onExecute: g, onEdit: _, onDelete: v, canUpdate: y, canDel
|
|
|
67
62
|
size: 10,
|
|
68
63
|
className: "absolute bottom-1.5 left-1.5 text-status-warning-text"
|
|
69
64
|
}),
|
|
70
|
-
/* @__PURE__ */ p(
|
|
65
|
+
/* @__PURE__ */ p(z, {
|
|
71
66
|
size: D ? 24 : 20,
|
|
72
67
|
className: D ? "text-text-primary" : "text-text-secondary"
|
|
73
68
|
}),
|
|
@@ -75,9 +70,9 @@ var h = ({ button: h, onExecute: g, onEdit: _, onDelete: v, canUpdate: y, canDel
|
|
|
75
70
|
className: `font-medium text-text-primary text-center leading-tight line-clamp-2 ${D ? "text-base" : "text-sm"}`,
|
|
76
71
|
children: h.label
|
|
77
72
|
}),
|
|
78
|
-
|
|
73
|
+
I && /* @__PURE__ */ p("span", {
|
|
79
74
|
className: "text-[10px] text-action-primary-bg/70 font-medium truncate max-w-full px-1",
|
|
80
|
-
children:
|
|
75
|
+
children: I
|
|
81
76
|
}),
|
|
82
77
|
h.category && /* @__PURE__ */ p("span", {
|
|
83
78
|
className: "text-[10px] text-text-tertiary uppercase tracking-wider",
|
|
@@ -165,15 +160,22 @@ var h = ({ button: h, onExecute: g, onEdit: _, onDelete: v, canUpdate: y, canDel
|
|
|
165
160
|
})
|
|
166
161
|
]
|
|
167
162
|
}),
|
|
168
|
-
!D && x && /* @__PURE__ */ p(
|
|
163
|
+
!D && x && /* @__PURE__ */ p(f.button, {
|
|
169
164
|
type: "button",
|
|
170
165
|
onClick: (e) => {
|
|
171
166
|
e.stopPropagation(), g(h.id);
|
|
172
167
|
},
|
|
173
168
|
disabled: S,
|
|
174
|
-
|
|
169
|
+
whileHover: { scale: 1.05 },
|
|
170
|
+
whileTap: { scale: .95 },
|
|
171
|
+
transition: { duration: .15 },
|
|
172
|
+
className: "absolute -top-3 left-1/2 -translate-x-1/2 size-14 rounded-full bg-action-primary-bg text-action-primary-text shadow-xl flex items-center justify-center disabled:opacity-50 disabled:cursor-not-allowed opacity-0 group-hover:opacity-100 focus-visible:opacity-100 transition-opacity duration-200 z-20",
|
|
175
173
|
title: "Execute",
|
|
176
|
-
|
|
174
|
+
"aria-label": "Execute button",
|
|
175
|
+
children: /* @__PURE__ */ p(s, {
|
|
176
|
+
size: 20,
|
|
177
|
+
className: "ml-0.5"
|
|
178
|
+
})
|
|
177
179
|
}),
|
|
178
180
|
!D && (y || b) && /* @__PURE__ */ m("div", {
|
|
179
181
|
className: "absolute bottom-0.5 right-0.5 flex gap-0.5 opacity-0 group-hover:opacity-100 transition-opacity",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"VibeDeckButtonTile.js","names":[],"sources":["../../../src/components/vibedeck/VibeDeckButtonTile.tsx"],"sourcesContent":["import type { FC } from 'react';\nimport { motion, AnimatePresence } from 'framer-motion';\nimport {\n Play,\n Pencil,\n Trash2,\n Terminal,\n Globe,\n FileCode,\n AlertTriangle,\n Check,\n X,\n Loader2,\n} from 'lucide-react';\nimport { SelectionCheckbox } from '../shared/SelectionCheckbox';\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}\n\ninterface VibeDeckButtonTileProps {\n button: ButtonData;\n onExecute: (id: string) => void;\n onEdit: (id: string) => void;\n onDelete: (id: string) => void;\n canUpdate: boolean;\n canDelete: boolean;\n canExecute: boolean;\n executing?: boolean;\n /** Visual feedback state after execution completes */\n executionStatus?: 'idle' | 'executing' | 'success' | 'error';\n isDragOver?: boolean;\n selected?: boolean;\n onToggleSelect?: (checked: boolean) => void;\n previewMode?: boolean;\n}\n\nexport const VibeDeckButtonTile: FC<VibeDeckButtonTileProps> = ({\n button,\n onExecute,\n onEdit,\n onDelete,\n canUpdate,\n canDelete,\n canExecute,\n executing = false,\n executionStatus = 'idle',\n isDragOver = false,\n selected = false,\n onToggleSelect,\n previewMode = false,\n}) => {\n const colorClasses: Record<string, string> = {\n red: 'border-status-error-border/30 bg-status-error-bg-subtle',\n green: 'border-status-success-border/30 bg-status-success-bg-subtle',\n blue: 'border-accent-blue/30 bg-accent-blue-subtle',\n yellow: 'border-status-warning-border/30 bg-status-warning-bg-subtle',\n purple: 'border-accent-purple/30 bg-accent-purple-subtle',\n orange: 'border-status-warning-border/30 bg-status-warning-bg-subtle',\n cyan: 'border-accent-teal/30 bg-accent-teal-subtle',\n pink: 'border-accent-purple/30 bg-accent-purple-subtle',\n };\n\n const previewColorClasses: Record<string, string> = {\n red: 'border-status-error-border/40 bg-gradient-to-br from-status-error-bg/15 to-status-error-bg/5 hover:shadow-status-error-bg/20',\n green:\n 'border-status-success-border/40 bg-gradient-to-br from-status-success-bg/15 to-status-success-bg/5 hover:shadow-status-success-bg/20',\n blue: 'border-accent-blue/40 bg-gradient-to-br from-accent-blue/15 to-accent-blue/5 hover:shadow-accent-blue/20',\n yellow:\n 'border-status-warning-border/40 bg-gradient-to-br from-status-warning-bg/15 to-status-warning-bg/5 hover:shadow-status-warning-bg/20',\n purple:\n 'border-accent-purple/40 bg-gradient-to-br from-accent-purple/15 to-accent-purple/5 hover:shadow-accent-purple/20',\n orange:\n 'border-status-warning-border/40 bg-gradient-to-br from-status-warning-bg/15 to-status-warning-bg/5 hover:shadow-status-warning-bg/20',\n cyan: 'border-accent-teal/40 bg-gradient-to-br from-accent-teal/15 to-accent-teal/5 hover:shadow-accent-teal/20',\n pink: 'border-accent-purple/40 bg-gradient-to-br from-accent-purple/15 to-accent-purple/5 hover:shadow-accent-purple/20',\n };\n\n const tileColor = previewMode\n ? button.color && previewColorClasses[button.color]\n ? previewColorClasses[button.color]\n : 'border-border-default bg-gradient-to-br from-bg-secondary to-bg-surface hover:shadow-action-primary-bg/10'\n : button.color && colorClasses[button.color]\n ? colorClasses[button.color]\n : 'border-border-default bg-bg-secondary';\n\n // Prefer action fields when linked, fall back to inline (deprecated) fields\n const effectiveActionType = button.action?.actionType || button.actionType;\n const effectiveCommand = button.action?.command ?? button.command;\n const effectiveScript = button.action?.script ?? null;\n const effectiveWebhookUrl = button.action?.webhookUrl ?? button.webhookUrl;\n const actionName = button.action?.displayName || button.action?.name || null;\n\n const isWebhook = effectiveActionType === 'WEBHOOK';\n const isScript = effectiveActionType === 'SCRIPT';\n const ActionIcon = isWebhook ? Globe : isScript ? FileCode : Terminal;\n const tooltip = isWebhook\n ? `${button.webhookMethod || 'POST'} ${effectiveWebhookUrl || ''}`\n : isScript\n ? `Script (${button.scriptInterpreter || 'bash'})${effectiveScript ? `: ${effectiveScript.slice(0, 60)}` : ''}`\n : `${effectiveCommand || ''}${button.keyboardShortcut ? ` (${button.keyboardShortcut})` : ''}`;\n\n // In preview mode, the entire tile is clickable to execute\n const handleTileClick = () => {\n if (previewMode && canExecute && !executing) {\n onExecute(button.id);\n }\n };\n\n // Status-dependent styles\n const statusRing =\n executionStatus === 'executing'\n ? 'ring-2 ring-action-primary-bg/60 ring-offset-1'\n : executionStatus === 'success'\n ? 'ring-2 ring-status-success-border/50 ring-offset-1'\n : executionStatus === 'error'\n ? 'ring-2 ring-status-error-border/50 ring-offset-1'\n : '';\n\n const statusBorderOverride =\n executionStatus === 'executing'\n ? 'border-action-primary-bg/60'\n : executionStatus === 'success'\n ? 'border-status-success-border/50'\n : executionStatus === 'error'\n ? 'border-status-error-border/50'\n : '';\n\n return (\n <motion.div\n className={`relative group h-full ${statusRing} ${isDragOver ? 'ring-2 ring-action-primary-bg ring-offset-1' : ''} ${selected ? 'ring-2 ring-action-primary-bg/40 ring-offset-1' : ''} ${previewMode && canExecute ? 'cursor-pointer' : ''} rounded-lg`}\n whileHover={{ scale: previewMode ? 1.04 : 1.02, transition: { duration: 0.15 } }}\n whileTap={previewMode ? { scale: 0.95, transition: { duration: 0.1 } } : { scale: 0.96 }}\n >\n <div\n role=\"button\"\n tabIndex={0}\n onKeyDown={(e) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n (handleTileClick as (...a: unknown[]) => unknown)();\n }\n }}\n onClick={handleTileClick}\n className={`w-full h-full min-h-[80px] rounded-lg border-2 p-3 flex flex-col items-center justify-center gap-2 transition-all ${tileColor} ${statusBorderOverride} ${executionStatus === 'executing' ? 'animate-pulse' : ''} ${previewMode ? 'hover:shadow-lg transition-shadow duration-200' : ''}`}\n title={tooltip}\n >\n {/* Top-left: webhook method or script interpreter badge */}\n {isWebhook && button.webhookMethod && (\n <span\n className={`absolute left-1.5 text-[9px] font-bold text-text-tertiary bg-bg-tertiary/80 rounded px-1 py-0.5 uppercase ${!previewMode && onToggleSelect ? 'top-8' : 'top-1.5'}`}\n >\n {button.webhookMethod}\n </span>\n )}\n {isScript && button.scriptInterpreter && (\n <span\n className={`absolute left-1.5 text-[9px] font-bold text-text-tertiary bg-bg-tertiary/80 rounded px-1 py-0.5 ${!previewMode && onToggleSelect ? 'top-8' : 'top-1.5'}`}\n >\n {button.scriptInterpreter}\n </span>\n )}\n\n {!previewMode && onToggleSelect && (\n <div\n className=\"absolute top-1.5 left-1.5 z-10\"\n onClick={(event) => event.stopPropagation()}\n >\n <SelectionCheckbox\n checked={selected}\n onChange={onToggleSelect}\n ariaLabel={`Select vibe deck button ${button.label}`}\n />\n </div>\n )}\n\n {/* Bottom-left: confirmation required warning */}\n {button.confirmationRequired && (\n <AlertTriangle\n size={10}\n className=\"absolute bottom-1.5 left-1.5 text-status-warning-text\"\n />\n )}\n\n <ActionIcon\n size={previewMode ? 24 : 20}\n className={previewMode ? 'text-text-primary' : 'text-text-secondary'}\n />\n <span\n className={`font-medium text-text-primary text-center leading-tight line-clamp-2 ${previewMode ? 'text-base' : 'text-sm'}`}\n >\n {button.label}\n </span>\n {actionName && (\n <span className=\"text-[10px] text-action-primary-bg/70 font-medium truncate max-w-full px-1\">\n {actionName}\n </span>\n )}\n {button.category && (\n <span className=\"text-[10px] text-text-tertiary uppercase tracking-wider\">\n {button.category}\n </span>\n )}\n\n {/* Execution status indicator */}\n <AnimatePresence>\n {executionStatus === 'executing' && (\n <motion.div\n initial={{ opacity: 0, scale: 0.8 }}\n animate={{ opacity: 1, scale: 1 }}\n exit={{ opacity: 0, scale: 0.8 }}\n className=\"absolute top-1.5 right-1.5 z-10\"\n >\n <div className=\"size-5 rounded-full bg-action-primary-bg flex items-center justify-center shadow-sm\">\n <Loader2 size={11} className=\"text-text-inverse animate-spin\" />\n </div>\n </motion.div>\n )}\n {executionStatus === 'success' && (\n <motion.div\n initial={{ opacity: 0, scale: 0.5 }}\n animate={{ opacity: 1, scale: 1 }}\n exit={{ opacity: 0, scale: 0.5 }}\n className=\"absolute top-1.5 right-1.5 z-10\"\n >\n <div className=\"size-5 rounded-full bg-status-success-bg flex items-center justify-center shadow-sm\">\n <Check size={11} className=\"text-text-inverse\" strokeWidth={3} />\n </div>\n </motion.div>\n )}\n {executionStatus === 'error' && (\n <motion.div\n initial={{ opacity: 0, scale: 0.5 }}\n animate={{ opacity: 1, scale: 1 }}\n exit={{ opacity: 0, scale: 0.5 }}\n className=\"absolute top-1.5 right-1.5 z-10\"\n >\n <div className=\"size-5 rounded-full bg-status-error-bg flex items-center justify-center shadow-sm\">\n <X size={11} className=\"text-text-inverse\" strokeWidth={3} />\n </div>\n </motion.div>\n )}\n </AnimatePresence>\n\n {/* Preview mode: centered play overlay on hover */}\n {previewMode && canExecute && executionStatus !== 'executing' && (\n <div className=\"absolute inset-0 rounded-lg flex items-center justify-center opacity-0 group-hover:opacity-100 transition-opacity duration-200 bg-bg-elevated/40 backdrop-blur-[1px]\">\n <div className=\"size-10 rounded-full bg-status-success-bg/90 flex items-center justify-center shadow-md\">\n <Play size={18} className=\"text-status-success-text ml-0.5\" />\n </div>\n </div>\n )}\n </div>\n\n {/* Top-right: Play button — visible in edit mode when user has execute permission */}\n {!previewMode && canExecute && (\n <button\n type=\"button\"\n onClick={(e) => {\n e.stopPropagation();\n onExecute(button.id);\n }}\n disabled={executing}\n className=\"absolute top-0.5 right-0.5 p-1 rounded bg-bg-elevated/80 text-status-success-text/60 hover:text-status-success-text hover:bg-bg-elevated transition-colors shadow-sm\"\n title=\"Execute\"\n >\n <Play size={12} />\n </button>\n )}\n\n {/* Bottom-right: Edit / Delete — visible on hover in edit mode only */}\n {!previewMode && (canUpdate || canDelete) && (\n <div className=\"absolute bottom-0.5 right-0.5 flex gap-0.5 opacity-0 group-hover:opacity-100 transition-opacity\">\n {canUpdate && (\n <button\n type=\"button\"\n onClick={(e) => {\n e.stopPropagation();\n onEdit(button.id);\n }}\n className=\"p-1 rounded bg-bg-elevated/80 hover:bg-bg-elevated text-text-tertiary hover:text-text-primary shadow-sm\"\n title=\"Edit\"\n >\n <Pencil size={12} />\n </button>\n )}\n {canDelete && (\n <button\n type=\"button\"\n onClick={(e) => {\n e.stopPropagation();\n onDelete(button.id);\n }}\n className=\"p-1 rounded bg-bg-elevated/80 hover:bg-bg-elevated text-text-tertiary hover:text-status-error-text shadow-sm\"\n title=\"Delete\"\n >\n <Trash2 size={12} />\n </button>\n )}\n </div>\n )}\n </motion.div>\n );\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"VibeDeckButtonTile.js","names":[],"sources":["../../../src/components/vibedeck/VibeDeckButtonTile.tsx"],"sourcesContent":["import type { FC } from 'react';\nimport { motion, AnimatePresence } from 'framer-motion';\nimport {\n Play,\n Pencil,\n Trash2,\n Terminal,\n Globe,\n FileCode,\n AlertTriangle,\n Check,\n X,\n Loader2,\n} from 'lucide-react';\nimport { SelectionCheckbox } from '../shared/SelectionCheckbox';\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}\n\ninterface VibeDeckButtonTileProps {\n button: ButtonData;\n onExecute: (id: string) => void;\n onEdit: (id: string) => void;\n onDelete: (id: string) => void;\n canUpdate: boolean;\n canDelete: boolean;\n canExecute: boolean;\n executing?: boolean;\n /** Visual feedback state after execution completes */\n executionStatus?: 'idle' | 'executing' | 'success' | 'error';\n isDragOver?: boolean;\n selected?: boolean;\n onToggleSelect?: (checked: boolean) => void;\n previewMode?: boolean;\n}\n\nexport const VibeDeckButtonTile: FC<VibeDeckButtonTileProps> = ({\n button,\n onExecute,\n onEdit,\n onDelete,\n canUpdate,\n canDelete,\n canExecute,\n executing = false,\n executionStatus = 'idle',\n isDragOver = false,\n selected = false,\n onToggleSelect,\n previewMode = false,\n}) => {\n const colorClasses: Record<string, string> = {\n red: 'border-status-error-border/30 bg-status-error-bg-subtle',\n green: 'border-status-success-border/30 bg-status-success-bg-subtle',\n blue: 'border-accent-blue/30 bg-accent-blue-subtle',\n yellow: 'border-status-warning-border/30 bg-status-warning-bg-subtle',\n purple: 'border-accent-purple/30 bg-accent-purple-subtle',\n orange: 'border-status-warning-border/30 bg-status-warning-bg-subtle',\n cyan: 'border-accent-teal/30 bg-accent-teal-subtle',\n pink: 'border-accent-purple/30 bg-accent-purple-subtle',\n };\n\n const previewColorClasses: Record<string, string> = {\n red: 'border-status-error-border/40 bg-gradient-to-br from-status-error-bg/15 to-status-error-bg/5 hover:shadow-status-error-bg/20',\n green:\n 'border-status-success-border/40 bg-gradient-to-br from-status-success-bg/15 to-status-success-bg/5 hover:shadow-status-success-bg/20',\n blue: 'border-accent-blue/40 bg-gradient-to-br from-accent-blue/15 to-accent-blue/5 hover:shadow-accent-blue/20',\n yellow:\n 'border-status-warning-border/40 bg-gradient-to-br from-status-warning-bg/15 to-status-warning-bg/5 hover:shadow-status-warning-bg/20',\n purple:\n 'border-accent-purple/40 bg-gradient-to-br from-accent-purple/15 to-accent-purple/5 hover:shadow-accent-purple/20',\n orange:\n 'border-status-warning-border/40 bg-gradient-to-br from-status-warning-bg/15 to-status-warning-bg/5 hover:shadow-status-warning-bg/20',\n cyan: 'border-accent-teal/40 bg-gradient-to-br from-accent-teal/15 to-accent-teal/5 hover:shadow-accent-teal/20',\n pink: 'border-accent-purple/40 bg-gradient-to-br from-accent-purple/15 to-accent-purple/5 hover:shadow-accent-purple/20',\n };\n\n // Glass-button visual: frosted background + soft border + shadow that\n // intensifies on hover. Per the round-3 streamdeck redesign, both\n // preview and edit modes share the same base treatment so the deck\n // feels consistent — only the per-color tint differs.\n const glassBase =\n 'border-border-default bg-bg-elevated/80 backdrop-blur-md shadow-lg hover:border-border-focus hover:shadow-xl';\n const tileColor = previewMode\n ? button.color && previewColorClasses[button.color]\n ? `${previewColorClasses[button.color]} backdrop-blur-md shadow-lg hover:shadow-xl`\n : glassBase\n : button.color && colorClasses[button.color]\n ? `${colorClasses[button.color]} backdrop-blur-md shadow-md hover:shadow-lg`\n : glassBase;\n\n // Prefer action fields when linked, fall back to inline (deprecated) fields\n const effectiveActionType = button.action?.actionType || button.actionType;\n const effectiveCommand = button.action?.command ?? button.command;\n const effectiveScript = button.action?.script ?? null;\n const effectiveWebhookUrl = button.action?.webhookUrl ?? button.webhookUrl;\n const actionName = button.action?.displayName || button.action?.name || null;\n\n const isWebhook = effectiveActionType === 'WEBHOOK';\n const isScript = effectiveActionType === 'SCRIPT';\n const ActionIcon = isWebhook ? Globe : isScript ? FileCode : Terminal;\n const tooltip = isWebhook\n ? `${button.webhookMethod || 'POST'} ${effectiveWebhookUrl || ''}`\n : isScript\n ? `Script (${button.scriptInterpreter || 'bash'})${effectiveScript ? `: ${effectiveScript.slice(0, 60)}` : ''}`\n : `${effectiveCommand || ''}${button.keyboardShortcut ? ` (${button.keyboardShortcut})` : ''}`;\n\n // In preview mode, the entire tile is clickable to execute\n const handleTileClick = () => {\n if (previewMode && canExecute && !executing) {\n onExecute(button.id);\n }\n };\n\n // Status-dependent styles\n const statusRing =\n executionStatus === 'executing'\n ? 'ring-2 ring-action-primary-bg/60 ring-offset-1'\n : executionStatus === 'success'\n ? 'ring-2 ring-status-success-border/50 ring-offset-1'\n : executionStatus === 'error'\n ? 'ring-2 ring-status-error-border/50 ring-offset-1'\n : '';\n\n const statusBorderOverride =\n executionStatus === 'executing'\n ? 'border-action-primary-bg/60'\n : executionStatus === 'success'\n ? 'border-status-success-border/50'\n : executionStatus === 'error'\n ? 'border-status-error-border/50'\n : '';\n\n // Calm hover + tap motion — drops the previous jumpy executing\n // `animate={{scale}}` loop. Per round-3 UX, the whole tile is the click\n // target so we apply the press-down motion at the wrapper level.\n const tileClickable = previewMode ? canExecute && !executing : canUpdate;\n\n return (\n <motion.div\n className={`relative group h-full ${statusRing} ${isDragOver ? 'ring-2 ring-action-primary-bg ring-offset-1' : ''} ${selected ? 'ring-2 ring-action-primary-bg/40 ring-offset-1' : ''} ${tileClickable ? 'cursor-pointer' : ''} rounded-lg focus-visible:ring-2 focus-visible:ring-border-focus focus-visible:outline-none`}\n whileHover={tileClickable ? { scale: 1.03 } : undefined}\n whileTap={tileClickable ? { scale: 0.97 } : undefined}\n transition={{ duration: 0.15 }}\n >\n <div\n role=\"button\"\n tabIndex={0}\n onKeyDown={(e) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n (handleTileClick as (...a: unknown[]) => unknown)();\n }\n }}\n onClick={handleTileClick}\n className={`w-full h-full min-h-[80px] rounded-lg border-2 p-3 flex flex-col items-center justify-center gap-2 transition-all duration-200 ${tileColor} ${statusBorderOverride} ${executionStatus === 'executing' ? 'animate-pulse' : ''}`}\n title={tooltip}\n >\n {/* Top-left: webhook method or script interpreter badge */}\n {isWebhook && button.webhookMethod && (\n <span\n className={`absolute left-1.5 text-[9px] font-bold text-text-tertiary bg-bg-tertiary/80 rounded px-1 py-0.5 uppercase ${!previewMode && onToggleSelect ? 'top-8' : 'top-1.5'}`}\n >\n {button.webhookMethod}\n </span>\n )}\n {isScript && button.scriptInterpreter && (\n <span\n className={`absolute left-1.5 text-[9px] font-bold text-text-tertiary bg-bg-tertiary/80 rounded px-1 py-0.5 ${!previewMode && onToggleSelect ? 'top-8' : 'top-1.5'}`}\n >\n {button.scriptInterpreter}\n </span>\n )}\n\n {!previewMode && onToggleSelect && (\n <div\n className=\"absolute top-1.5 left-1.5 z-10\"\n onClick={(event) => event.stopPropagation()}\n >\n <SelectionCheckbox\n checked={selected}\n onChange={onToggleSelect}\n ariaLabel={`Select vibe deck button ${button.label}`}\n />\n </div>\n )}\n\n {/* Bottom-left: confirmation required warning */}\n {button.confirmationRequired && (\n <AlertTriangle\n size={10}\n className=\"absolute bottom-1.5 left-1.5 text-status-warning-text\"\n />\n )}\n\n <ActionIcon\n size={previewMode ? 24 : 20}\n className={previewMode ? 'text-text-primary' : 'text-text-secondary'}\n />\n <span\n className={`font-medium text-text-primary text-center leading-tight line-clamp-2 ${previewMode ? 'text-base' : 'text-sm'}`}\n >\n {button.label}\n </span>\n {actionName && (\n <span className=\"text-[10px] text-action-primary-bg/70 font-medium truncate max-w-full px-1\">\n {actionName}\n </span>\n )}\n {button.category && (\n <span className=\"text-[10px] text-text-tertiary uppercase tracking-wider\">\n {button.category}\n </span>\n )}\n\n {/* Execution status indicator */}\n <AnimatePresence>\n {executionStatus === 'executing' && (\n <motion.div\n initial={{ opacity: 0, scale: 0.8 }}\n animate={{ opacity: 1, scale: 1 }}\n exit={{ opacity: 0, scale: 0.8 }}\n className=\"absolute top-1.5 right-1.5 z-10\"\n >\n <div className=\"size-5 rounded-full bg-action-primary-bg flex items-center justify-center shadow-sm\">\n <Loader2 size={11} className=\"text-text-inverse animate-spin\" />\n </div>\n </motion.div>\n )}\n {executionStatus === 'success' && (\n <motion.div\n initial={{ opacity: 0, scale: 0.5 }}\n animate={{ opacity: 1, scale: 1 }}\n exit={{ opacity: 0, scale: 0.5 }}\n className=\"absolute top-1.5 right-1.5 z-10\"\n >\n <div className=\"size-5 rounded-full bg-status-success-bg flex items-center justify-center shadow-sm\">\n <Check size={11} className=\"text-text-inverse\" strokeWidth={3} />\n </div>\n </motion.div>\n )}\n {executionStatus === 'error' && (\n <motion.div\n initial={{ opacity: 0, scale: 0.5 }}\n animate={{ opacity: 1, scale: 1 }}\n exit={{ opacity: 0, scale: 0.5 }}\n className=\"absolute top-1.5 right-1.5 z-10\"\n >\n <div className=\"size-5 rounded-full bg-status-error-bg flex items-center justify-center shadow-sm\">\n <X size={11} className=\"text-text-inverse\" strokeWidth={3} />\n </div>\n </motion.div>\n )}\n </AnimatePresence>\n\n {/* Preview mode: centered play overlay on hover */}\n {previewMode && canExecute && executionStatus !== 'executing' && (\n <div className=\"absolute inset-0 rounded-lg flex items-center justify-center opacity-0 group-hover:opacity-100 transition-opacity duration-200 bg-bg-elevated/40 backdrop-blur-[1px]\">\n <div className=\"size-10 rounded-full bg-status-success-bg/90 flex items-center justify-center shadow-md\">\n <Play size={18} className=\"text-status-success-text ml-0.5\" />\n </div>\n </div>\n )}\n </div>\n\n {/* Prominent Play button — calm, centred-top, glass-styled.\n Round-3 redesign drops the jumpy scale loop and bumps the size\n so the action is obvious in edit mode without competing with\n the tile's drag handle on hover. */}\n {!previewMode && canExecute && (\n <motion.button\n type=\"button\"\n onClick={(e) => {\n e.stopPropagation();\n onExecute(button.id);\n }}\n disabled={executing}\n whileHover={{ scale: 1.05 }}\n whileTap={{ scale: 0.95 }}\n transition={{ duration: 0.15 }}\n className=\"absolute -top-3 left-1/2 -translate-x-1/2 size-14 rounded-full bg-action-primary-bg text-action-primary-text shadow-xl flex items-center justify-center disabled:opacity-50 disabled:cursor-not-allowed opacity-0 group-hover:opacity-100 focus-visible:opacity-100 transition-opacity duration-200 z-20\"\n title=\"Execute\"\n aria-label=\"Execute button\"\n >\n <Play size={20} className=\"ml-0.5\" />\n </motion.button>\n )}\n\n {/* Bottom-right: Edit / Delete — visible on hover in edit mode only */}\n {!previewMode && (canUpdate || canDelete) && (\n <div className=\"absolute bottom-0.5 right-0.5 flex gap-0.5 opacity-0 group-hover:opacity-100 transition-opacity\">\n {canUpdate && (\n <button\n type=\"button\"\n onClick={(e) => {\n e.stopPropagation();\n onEdit(button.id);\n }}\n className=\"p-1 rounded bg-bg-elevated/80 hover:bg-bg-elevated text-text-tertiary hover:text-text-primary shadow-sm\"\n title=\"Edit\"\n >\n <Pencil size={12} />\n </button>\n )}\n {canDelete && (\n <button\n type=\"button\"\n onClick={(e) => {\n e.stopPropagation();\n onDelete(button.id);\n }}\n className=\"p-1 rounded bg-bg-elevated/80 hover:bg-bg-elevated text-text-tertiary hover:text-status-error-text shadow-sm\"\n title=\"Delete\"\n >\n <Trash2 size={12} />\n </button>\n )}\n </div>\n )}\n </motion.div>\n );\n};\n"],"mappings":"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"}
|
|
@@ -107,9 +107,9 @@ var s = ({ gridColumns: s, gridRows: c, buttons: l, onExecuteButton: u, onEditBu
|
|
|
107
107
|
}) : g ? /* @__PURE__ */ o("button", {
|
|
108
108
|
type: "button",
|
|
109
109
|
onClick: () => m(t, n),
|
|
110
|
-
className: "w-full h-full min-h-[80px] rounded-lg border-2 border-dashed border-border-
|
|
110
|
+
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-secondary/50",
|
|
111
111
|
children: /* @__PURE__ */ o(a, { size: 18 })
|
|
112
|
-
}) : /* @__PURE__ */ o("div", { className: "w-full h-full min-h-[80px] rounded-lg border border-border-default
|
|
112
|
+
}) : /* @__PURE__ */ o("div", { className: "w-full h-full min-h-[80px] rounded-lg border-2 border-dashed border-border-default" })
|
|
113
113
|
}, r));
|
|
114
114
|
}
|
|
115
115
|
}
|
|
@@ -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\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}) => {\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 />\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-action-primary-bg/10 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-default/50 hover:border-border-default flex items-center justify-center text-text-tertiary hover:text-text-secondary transition-all hover:bg-bg-secondary/50\"\n >\n <Plus size={18} />\n </button>\n ) : (\n <div className=\"w-full h-full min-h-[80px] rounded-lg border border-border-default/20\" />\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\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}) => {\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 />\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-action-primary-bg/10 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-secondary/50\"\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,2 +1,2 @@
|
|
|
1
|
-
@keyframes fade-in{0%{opacity:0;transform:translateY(4px)}to{opacity:1;transform:translateY(0)}}@keyframes slide-in-right{0%{opacity:0;transform:translate(8px)}to{opacity:1;transform:translate(0)}}@keyframes pulse-subtle{0%,to{opacity:1}50%{opacity:.7}}@keyframes shrink-width{0%{width:100%}to{width:0%}}.animate-fade-in{animation:fade-in var(--motion-duration-fast,.15s) ease-out forwards}.animate-slide-in-right{animation:slide-in-right var(--motion-duration-fast,.15s) ease-out forwards}.animate-pulse-subtle{animation:2s ease-in-out infinite pulse-subtle}.animate-shrink-width{animation:linear forwards shrink-width}.react-flow__pane{touch-action:none!important}.react-flow__edge{cursor:pointer}.react-flow__edge .react-flow__edge-path{transition:stroke-width .16s,filter .16s}.react-flow__edge:hover .react-flow__edge-path{stroke-width:3px}.react-flow__edge.selected .react-flow__edge-path{stroke-width:3px;filter:drop-shadow(0 0 2px var(--color-action-primary-bg))}
|
|
1
|
+
@media print{[data-app-shell-sidenav],[data-app-shell-header],nav[aria-label=Primary],.vc-print-hide{display:none!important}body{color:#000!important;background:#fff!important}.vc-printable{background:0 0!important;width:100%!important;max-width:none!important;margin:0!important;padding:0!important}.vc-printable .recharts-wrapper,.vc-printable .recharts-surface{width:100%!important;max-width:100%!important}table{page-break-inside:auto}tr{page-break-inside:avoid;page-break-after:auto}}@keyframes fade-in{0%{opacity:0;transform:translateY(4px)}to{opacity:1;transform:translateY(0)}}@keyframes slide-in-right{0%{opacity:0;transform:translate(8px)}to{opacity:1;transform:translate(0)}}@keyframes pulse-subtle{0%,to{opacity:1}50%{opacity:.7}}@keyframes shrink-width{0%{width:100%}to{width:0%}}.animate-fade-in{animation:fade-in var(--motion-duration-fast,.15s) ease-out forwards}.animate-slide-in-right{animation:slide-in-right var(--motion-duration-fast,.15s) ease-out forwards}.animate-pulse-subtle{animation:2s ease-in-out infinite pulse-subtle}.animate-shrink-width{animation:linear forwards shrink-width}.react-flow__pane{touch-action:none!important}.react-flow__edge{cursor:pointer}.react-flow__edge .react-flow__edge-path{transition:stroke-width .16s,filter .16s}.react-flow__edge:hover .react-flow__edge-path{stroke-width:3px}.react-flow__edge.selected .react-flow__edge-path{stroke-width:3px;filter:drop-shadow(0 0 2px var(--color-action-primary-bg))}
|
|
2
2
|
/*$vite$:1*/
|
|
@@ -1363,7 +1363,7 @@ function bt() {
|
|
|
1363
1363
|
return /* @__PURE__ */ C("button", {
|
|
1364
1364
|
type: "button",
|
|
1365
1365
|
onClick: () => kn(e.id),
|
|
1366
|
-
className: `flex shrink-0 items-center gap-1.5 px-3 py-2 text-xs sm:text-sm font-medium border-b-2 transition-colors whitespace-nowrap ${t ? "border-action-primary-bg text-action-primary-
|
|
1366
|
+
className: `flex shrink-0 items-center gap-1.5 px-3 py-2 text-xs sm:text-sm font-medium border-b-2 transition-colors whitespace-nowrap ${t ? "border-action-primary-bg text-action-primary-bg" : "border-transparent text-text-secondary hover:text-text-primary hover:border-border-default"}`,
|
|
1367
1367
|
children: [
|
|
1368
1368
|
/* @__PURE__ */ S(n, { className: "size-3.5" }),
|
|
1369
1369
|
e.label,
|
|
@@ -1487,7 +1487,7 @@ function bt() {
|
|
|
1487
1487
|
type: "button",
|
|
1488
1488
|
onClick: () => Hn(e.packageName),
|
|
1489
1489
|
disabled: xn === e.packageName,
|
|
1490
|
-
className: "flex items-center gap-1.5 px-2.5 py-1.5 text-xs text-status-error-text bg-status-error-bg rounded-md hover:bg-status-error-bg/
|
|
1490
|
+
className: "flex items-center gap-1.5 px-2.5 py-1.5 text-xs font-medium text-status-error-text bg-status-error-bg/15 border border-status-error-border/40 rounded-md hover:bg-status-error-bg/25 transition-colors disabled:opacity-50 disabled:cursor-not-allowed",
|
|
1491
1491
|
children: [xn === e.packageName ? /* @__PURE__ */ S(_, { className: "size-3 animate-spin" }) : /* @__PURE__ */ S(lt, { className: "size-3" }), "Remove"]
|
|
1492
1492
|
})]
|
|
1493
1493
|
})]
|