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
2 changes: 1 addition & 1 deletion apps/docs/features/ui/CodeBlock/CodeBlock.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ export async function CodeBlock({
'group',
'relative',
'not-prose',
'w-full overflow-hidden',
'w-full overflow-x-auto',
'border border-default rounded-lg',
'bg-200',
'text-sm',
Expand Down
5 changes: 4 additions & 1 deletion apps/docs/styles/main.scss
Original file line number Diff line number Diff line change
Expand Up @@ -359,8 +359,11 @@ th code {
}

/* Word wrap styles for code blocks */
.shiki[data-wrapped='true'] .code-content {
.shiki[data-wrapped='true'] {
overflow-x: hidden !important;
}

.shiki[data-wrapped='true'] .code-content {
white-space: pre-wrap !important;
word-break: break-word !important;
}
96 changes: 53 additions & 43 deletions apps/studio/components/grid/SupabaseGrid.utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,15 @@ import { CalculatedColumn, CellKeyboardEvent } from 'react-data-grid'

import type { Filter, SavedState } from 'components/grid/types'
import { Entity, isTableLike } from 'data/table-editor/table-editor-types'
import { BASE_PATH } from 'lib/constants'
import { useSearchParams } from 'next/navigation'
import { parseAsBoolean, parseAsNativeArrayOf, parseAsString, useQueryStates } from 'nuqs'
import { parseAsNativeArrayOf, parseAsString, useQueryStates } from 'nuqs'
import { copyToClipboard } from 'ui'
import { FilterOperatorOptions } from './components/header/filter/Filter.constants'
import { STORAGE_KEY_PREFIX } from './constants'
import type { Sort, SupaColumn, SupaTable } from './types'
import { formatClipboardValue } from './utils/common'

export const LOAD_TAB_FROM_CACHE_PARAM = 'loadFromCache'

export function formatSortURLParams(tableName: string, sort?: string[]): Sort[] {
if (Array.isArray(sort)) {
return compact(
Expand All @@ -29,7 +28,7 @@ export function formatSortURLParams(tableName: string, sort?: string[]): Sort[]
return []
}

export function sortsToUrlParams(sorts: Sort[]) {
export function sortsToUrlParams(sorts: { column: string; ascending?: boolean }[]) {
return sorts.map((sort) => `${sort.column}:${sort.ascending ? 'asc' : 'desc'}`)
}

Expand All @@ -54,7 +53,9 @@ export function formatFilterURLParams(filter?: string[]): Filter[] {
) as Filter[]
}

export function filtersToUrlParams(filters: Filter[]) {
export function filtersToUrlParams(
filters: { column: string | Array<string>; operator: string; value: string }[]
) {
return filters.map((filter) => {
const selectedOperator = FilterOperatorOptions.find(
(option) => option.value === filter.operator
Expand Down Expand Up @@ -134,50 +135,75 @@ export function getStorageKey(prefix: string, ref: string) {

export function loadTableEditorStateFromLocalStorage(
projectRef: string,
tableName: string,
schema?: string | null
tableId: number
): SavedState | undefined {
const storageKey = getStorageKey(STORAGE_KEY_PREFIX, projectRef)
// Prefer sessionStorage (scoped to current tab) over localStorage
const jsonStr = sessionStorage.getItem(storageKey) ?? localStorage.getItem(storageKey)
if (!jsonStr) return
const json = JSON.parse(jsonStr)
const tableKey = !schema || schema == 'public' ? tableName : `${schema}.${tableName}`
return json[tableKey]
return json[tableId]
}

/**
* Builds a table editor URL with the given project reference, table ID. It will load the saved state from local storage
* and add the sort and filter parameters to the URL.
*/
export function buildTableEditorUrl({
projectRef = 'default',
tableId,
schema,
}: {
projectRef?: string
tableId: number
schema?: string
}) {
const url = new URL(`${BASE_PATH}/project/${projectRef}/editor/${tableId}`, location.origin)

// If the schema is provided, add it to the URL so that the left sidebar is opened to the correct schema
if (schema) {
url.searchParams.set('schema', schema)
}

const savedState = loadTableEditorStateFromLocalStorage(projectRef, tableId)
if (savedState?.sorts && savedState.sorts.length > 0) {
savedState.sorts?.forEach((sort) => url.searchParams.append('sort', sort))
}
if (savedState?.filters && savedState.filters.length > 0) {
savedState.filters?.forEach((filter) => url.searchParams.append('filter', filter))
}
return url.toString()
}

export function saveTableEditorStateToLocalStorage({
projectRef,
tableName,
schema,
tableId,
gridColumns,
sorts,
filters,
}: {
projectRef: string
tableName: string
schema?: string | null
tableId: number
gridColumns?: CalculatedColumn<any, any>[]
sorts?: string[]
filters?: string[]
}) {
const storageKey = getStorageKey(STORAGE_KEY_PREFIX, projectRef)
const savedStr = sessionStorage.getItem(storageKey) ?? localStorage.getItem(storageKey)
const tableKey = !schema || schema == 'public' ? tableName : `${schema}.${tableName}`

const config = {
...(gridColumns !== undefined && { gridColumns }),
...(sorts !== undefined && { sorts }),
...(filters !== undefined && { filters }),
...(sorts !== undefined && { sorts: sorts.filter((sort) => sort !== '') }),
...(filters !== undefined && { filters: filters.filter((filter) => filter !== '') }),
}

let savedJson
if (savedStr) {
savedJson = JSON.parse(savedStr)
const previousConfig = savedJson[tableKey]
savedJson = { ...savedJson, [tableKey]: { ...previousConfig, ...config } }
const previousConfig = savedJson[tableId]
savedJson = { ...savedJson, [tableId]: { ...previousConfig, ...config } }
} else {
savedJson = { [tableKey]: config }
savedJson = { [tableId]: config }
}
// Save to both localStorage and sessionStorage so it's consistent to current tab
localStorage.setItem(storageKey, JSON.stringify(savedJson))
Expand All @@ -193,8 +219,7 @@ function getLatestParams() {
const queryParams = new URLSearchParams(window.location.search)
const sort = queryParams.getAll('sort')
const filter = queryParams.getAll('filter')
const loadFromCache = !!queryParams.get(LOAD_TAB_FROM_CACHE_PARAM)
return { sort, filter, loadFromCache }
return { sort, filter }
}

export function useSyncTableEditorStateFromLocalStorageWithUrl({
Expand All @@ -209,7 +234,6 @@ export function useSyncTableEditorStateFromLocalStorageWithUrl({
{
sort: parseAsNativeArrayOf(parseAsString),
filter: parseAsNativeArrayOf(parseAsString),
[LOAD_TAB_FROM_CACHE_PARAM]: parseAsBoolean.withDefault(false),
},
{
history: 'replace',
Expand All @@ -220,8 +244,7 @@ export function useSyncTableEditorStateFromLocalStorageWithUrl({
const urlParams = useMemo(() => {
const sort = searchParams.getAll('sort')
const filter = searchParams.getAll('filter')
const loadFromCache = !!searchParams.get(LOAD_TAB_FROM_CACHE_PARAM)
return { sort, filter, loadFromCache }
return { sort, filter }
}, [searchParams])

useEffect(() => {
Expand All @@ -232,25 +255,12 @@ export function useSyncTableEditorStateFromLocalStorageWithUrl({
// `urlParams` from `useQueryStates` can be stale so always get the latest from the URL
const latestUrlParams = getLatestParams()

if (latestUrlParams.loadFromCache) {
const savedState = loadTableEditorStateFromLocalStorage(projectRef, table.name, table.schema)
updateUrlParams(
{
sort: savedState?.sorts ?? [],
filter: savedState?.filters ?? [],
loadFromCache: false,
},
{ clearOnDefault: true }
)
} else {
saveTableEditorStateToLocalStorage({
projectRef,
tableName: table.name,
schema: table.schema,
sorts: latestUrlParams.sort,
filters: latestUrlParams.filter,
})
}
saveTableEditorStateToLocalStorage({
projectRef,
tableId: table.id,
sorts: latestUrlParams.sort,
filters: latestUrlParams.filter,
})
}, [urlParams, table, projectRef])
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,10 @@ export const BranchRow = ({
const page = router.pathname.split('/').pop()

const daysFromNow = dayjs().diff(dayjs(branch.updated_at), 'day')
const willBeDeletedIn = branch.deletion_scheduled_at
? dayjs(branch.deletion_scheduled_at).diff(dayjs(), 'minutes')
: null
const isDeletionPending = willBeDeletedIn !== null && willBeDeletedIn < 0
const formattedTimeFromNow = dayjs(branch.updated_at).fromNow()
const formattedUpdatedAt = dayjs(branch.updated_at).format('DD MMM YYYY, HH:mm:ss (ZZ)')

Expand Down Expand Up @@ -125,9 +129,19 @@ export const BranchRow = ({
</Tooltip>
</div>
<div className="flex items-center gap-x-4">
<p className="text-xs text-foreground-lighter">
{daysFromNow > 1 ? `Updated on ${formattedUpdatedAt}` : `Updated ${formattedTimeFromNow}`}
</p>
{branch.deletion_scheduled_at ? (
<p className="text-xs text-foreground-lighter">
{isDeletionPending
? 'Deletion pending...'
: `Will be deleted in ${willBeDeletedIn} minutes`}
</p>
) : (
<p className="text-xs text-foreground-lighter">
{daysFromNow > 1
? `Updated on ${formattedUpdatedAt}`
: `Updated ${formattedTimeFromNow}`}
</p>
)}
<WorkflowLogs projectRef={branch.project_ref} status={branch.status} />
{rowActions}
</div>
Expand Down
Loading
Loading