Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions apps/docs/content/guides/deployment/branching.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ Supabase branches create separate environments that spin off from your main proj
## How branching works

- **Separate Environments**: Each branch is a separate environment with its own Supabase instance and API credentials.
- **Preview Branches**: You can create multiple Preview Branches for testing.
- **Persistent Branches**: Persistent branches are long-lived branches. They aren't automatically paused or deleted due to non-inactivity or merging.
- **Preview Branches**: Preview branches are ephemeral and best suited for focused testing. They are automatically paused after inactivity or deleted when a PR is merged or closed.
- **Persistent Branches**: Persistent branches are long-lived and recommended for environments like staging, QA, or development. Unlike preview branches, they aren't automatically paused or deleted due to inactivity or when a PR is merged or closed.
- **Managing Branches**: You can create, review, and merge branches either automatically via our [GitHub integration](/docs/guides/deployment/branching/github-integration) or directly [through the dashboard](/docs/guides/deployment/branching/dashboard) (currently in beta). All branches show up in the branches page in the dashboard, regardless of how they were created.
- **Data-less**: New branches do not start with any data from your main project. This is meant to better protect your sensitive production data. To start your branches with data, you can use a [seed file](/docs/guides/deployment/branching/github-integration#seeding) if using the GitHub integration.

Expand Down
1 change: 1 addition & 0 deletions apps/docs/public/humans.txt
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ Eliot Whalan
Emmett Folger
Eric Kharitonashvili
Etienne Stalmans
Eyal Ehrlich
Fabrizio Cataldo
Fabrizio Fenoglio
Fady A
Expand Down
Original file line number Diff line number Diff line change
@@ -1,38 +1,30 @@
import { Eye } from 'lucide-react'
import { useOperationQueueActions } from 'components/grid/hooks/useOperationQueueActions'
import { useOperationQueueShortcuts } from 'components/grid/hooks/useOperationQueueShortcuts'
import { useIsQueueOperationsEnabled } from 'components/interfaces/App/FeaturePreview/FeaturePreviewContext'
import { AnimatePresence, motion } from 'framer-motion'
import { Eye } from 'lucide-react'
import { createPortal } from 'react-dom'
import { useTableEditorStateSnapshot } from 'state/table-editor'
import { Button } from 'ui'

import {
useOperationQueueShortcuts,
getModKey,
} from 'components/grid/hooks/useOperationQueueShortcuts'
import { useIsQueueOperationsEnabled } from 'components/interfaces/App/FeaturePreview/FeaturePreviewContext'
import { useTableEditorStateSnapshot } from 'state/table-editor'
import { useOperationQueueActions } from 'components/grid/hooks/useOperationQueueActions'
import { getModKeyLabel } from '@/lib/helpers'

const modKey = getModKeyLabel()

export const SaveQueueActionBar = () => {
const snap = useTableEditorStateSnapshot()
const isQueueOperationsEnabled = useIsQueueOperationsEnabled()
const { handleSave } = useOperationQueueActions()

useOperationQueueShortcuts()

const operationCount = snap.operationQueue.operations.length
const isSaving = snap.operationQueue.status === 'saving'
const isOperationQueuePanelOpen = snap.sidePanel?.type === 'operation-queue'

const isVisible =
isQueueOperationsEnabled && snap.hasPendingOperations && !isOperationQueuePanelOpen

useOperationQueueShortcuts({
enabled: isQueueOperationsEnabled && snap.hasPendingOperations,
onSave: handleSave,
onTogglePanel: () => snap.onViewOperationQueue(),
isSaving,
hasOperations: operationCount > 0,
})

const modKey = getModKey()

const content = (
<AnimatePresence>
{isVisible && (
Expand All @@ -49,7 +41,7 @@ export const SaveQueueActionBar = () => {
</span>
<div className="flex items-center gap-3">
<button
onClick={() => snap.onViewOperationQueue()}
onClick={() => snap.toggleViewOperationQueue()}
className="text-foreground-light hover:text-foreground transition-colors flex items-center"
aria-label="View Details"
>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
import { THRESHOLD_COUNT } from '@supabase/pg-meta/src/query/table-row-query'
import { keepPreviousData } from '@tanstack/react-query'
import { isEqual } from 'lodash'
import { ChevronDown, List } from 'lucide-react'
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'

import { useParams } from 'common'
import { useTableFilter } from 'components/grid/hooks/useTableFilter'
import type { Sort } from 'components/grid/types'
import { InlineLink } from 'components/ui/InlineLink'
import { useTableRowsCountQuery } from 'data/table-rows/table-rows-count-query'
import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject'
import { isEqual } from 'lodash'
import { ChevronDown, List } from 'lucide-react'
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import {
type RoleImpersonationState,
useRoleImpersonationStateSnapshot,
Expand All @@ -23,6 +22,7 @@ import {
Popover_Shadcn_,
} from 'ui'
import ConfirmationModal from 'ui-patterns/Dialogs/ConfirmationModal'

import { DropdownControl } from '../../common/DropdownControl'
import SortRow from './SortRow'

Expand Down Expand Up @@ -278,8 +278,10 @@ export const SortPopoverPrimitive = ({
const hasSortNotPK = localSorts.some(
(x) => !snap.table.columns.find((y) => x.column === y.name)?.isPrimaryKey
)
if (hasSortNotPK) setShowWarning(true)
} else onSelectApplySorts()
if (hasSortNotPK) return setShowWarning(true)
}

onSelectApplySorts()
}}
>
Apply sorting
Expand Down
14 changes: 6 additions & 8 deletions apps/studio/components/grid/hooks/useOperationQueueActions.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import { useQueryClient } from '@tanstack/react-query'
import { useCallback } from 'react'
import { toast } from 'sonner'

import { tableRowKeys } from 'data/table-rows/keys'
import { useOperationQueueSaveMutation } from 'data/table-rows/operation-queue-save-mutation'
import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject'
import { useCallback } from 'react'
import { toast } from 'sonner'
import { useGetImpersonatedRoleState } from 'state/role-impersonation-state'
import { useTableEditorStateSnapshot } from 'state/table-editor'
import { QueuedOperation } from 'state/table-editor-operation-queue.types'
Expand All @@ -30,6 +29,7 @@ export function useOperationQueueActions(options: UseOperationQueueActionsOption
useOperationQueueSaveMutation({
onSuccess: () => {
snap.clearQueue()
snap.closeSidePanel()
toast.success('Changes saved successfully')
onSaveSuccess?.()
},
Expand All @@ -40,12 +40,10 @@ export function useOperationQueueActions(options: UseOperationQueueActionsOption
})

const isSaving = snap.operationQueue.status === 'saving' || isMutationPending
const operations = snap.operationQueue.operations as readonly QueuedOperation[]

const handleSave = useCallback(() => {
if (!project) return

const operations = snap.operationQueue.operations as readonly QueuedOperation[]
if (operations.length === 0) return
if (!project || operations.length === 0) return

snap.setQueueStatus('saving')

Expand All @@ -55,7 +53,7 @@ export function useOperationQueueActions(options: UseOperationQueueActionsOption
operations,
roleImpersonationState: getImpersonatedRoleState(),
})
}, [snap, project, saveOperationQueue, getImpersonatedRoleState])
}, [snap, project, operations, saveOperationQueue, getImpersonatedRoleState])

const handleCancel = useCallback(() => {
// Get unique table IDs from the queue before clearing
Expand Down
82 changes: 30 additions & 52 deletions apps/studio/components/grid/hooks/useOperationQueueShortcuts.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,7 @@
import { useCallback, useEffect } from 'react'

import { detectOS } from 'lib/helpers'

export function getModKey() {
const os = detectOS()
return os === 'macos' ? '⌘' : 'Ctrl+'
}

interface UseOperationQueueShortcutsOptions {
enabled: boolean
onSave: () => void
onTogglePanel: () => void
isSaving?: boolean
hasOperations?: boolean
}
import { useOperationQueueActions } from './useOperationQueueActions'
import { useIsQueueOperationsEnabled } from '@/components/interfaces/App/FeaturePreview/FeaturePreviewContext'
import { useHotKey } from '@/hooks/ui/useHotKey'
import { useTableEditorStateSnapshot } from '@/state/table-editor'

/**
* Hook that provides keyboard shortcuts for the operation queue.
Expand All @@ -25,44 +13,34 @@ interface UseOperationQueueShortcutsOptions {
* These shortcuts are registered on the capture phase to ensure they fire
* before the data grid handles the keyboard event.
*/
export function useOperationQueueShortcuts({
enabled,
onSave,
onTogglePanel,
isSaving = false,
hasOperations = true,
}: UseOperationQueueShortcutsOptions) {
const os = detectOS()
const modKey = os === 'macos' ? '⌘' : 'Ctrl+'

const handleKeyDown = useCallback(
(event: KeyboardEvent) => {
const isMod = os === 'macos' ? event.metaKey : event.ctrlKey

if (isMod && event.key === 's') {
event.preventDefault()
event.stopPropagation()
if (!isSaving && hasOperations) {
onSave()
}
} else if (isMod && event.key === '.') {
event.preventDefault()
event.stopPropagation()
onTogglePanel()
export function useOperationQueueShortcuts() {
const isQueueOperationsEnabled = useIsQueueOperationsEnabled()
const snap = useTableEditorStateSnapshot()
const { handleSave } = useOperationQueueActions()

const isSaving = snap.operationQueue.status === 'saving'
const hasOperations = snap.hasPendingOperations
const isEnabled = isQueueOperationsEnabled && hasOperations

useHotKey(
(event) => {
event.preventDefault()
event.stopPropagation()
if (!isSaving && hasOperations) {
handleSave()
}
},
[os, isSaving, hasOperations, onSave, onTogglePanel]
's',
{ enabled: isEnabled }
)

// Use capture phase to intercept events before the grid handles them
useEffect(() => {
if (enabled) {
window.addEventListener('keydown', handleKeyDown, true)
return () => {
window.removeEventListener('keydown', handleKeyDown, true)
}
}
}, [enabled, handleKeyDown])

return { modKey }
useHotKey(
(event) => {
event.preventDefault()
event.stopPropagation()
snap.toggleViewOperationQueue()
},
'.',
{ enabled: isEnabled }
)
}
Loading
Loading