Files
supabase/apps/studio/components/interfaces/Database/Schemas/useExportSchemaToImage.ts
Alaister Young 7e9badc6b8 chore(studio): migrate useStaticEffectEvent to React 19 useEffectEvent (#46415)
Studio is on `react@^19.2.6`, and `useEffectEvent` shipped stable in
React 19.2 with the same signature as the userland polyfill. This drops
the local hook in `apps/studio` and `apps/www` in favor of the built-in.

**Removed:**
- `apps/studio/hooks/useStaticEffectEvent.ts`
- `apps/www/hooks/useStaticEffectEvent.ts`
- `.claude/skills/use-static-effect-event/` — skill is obsolete

**Changed:**
- 26 call sites: dropped the `useStaticEffectEvent` import, added
`useEffectEvent` to the existing `react` import, renamed call sites
- `.claude/CLAUDE.md`: `apps/studio` row updated React 18 → React 19
- `.claude/skills/vercel-composition-patterns/SKILL.md`: removed stale
"Studio uses React 18, skip these patterns" warning

## To test

- `pnpm typecheck --filter=studio` — passes locally
- `pnpm typecheck --filter=www` — passes locally
- `grep -rn "useStaticEffectEvent"` returns nothing outside
`node_modules`
- Smoke-test areas that use the hook: schema visualizer edges
(intersection check), spreadsheet import, sign-in/CLI login flows, side
panels with unsaved-changes prompts

**Out of scope:** pre-existing Tailwind lint warning on
`DefaultEdge.tsx:141` (`outline` + `outline-1` conflict) — unrelated to
this migration

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Refactor**
* Internal event handling migrated to React’s built-in event hooks
across the Studio app; no user-facing changes.

* **Documentation**
* Clarified React 19 compatibility and noted Studio now targets React
19.
  * Removed obsolete documentation for a deprecated internal hook.

<!-- review_stack_entry_start -->

[![Review Change
Stack](https://storage.googleapis.com/coderabbit_public_assets/review-stack-in-coderabbit-ui.svg)](https://app.coderabbit.ai/change-stack/supabase/supabase/pull/46415?utm_source=github_walkthrough&utm_medium=github&utm_campaign=change_stack)

<!-- review_stack_entry_end -->
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Alaister Young <10985857+alaister@users.noreply.github.com>
2026-05-28 23:30:42 +08:00

91 lines
2.7 KiB
TypeScript

import { toPng, toSvg } from 'html-to-image'
import { useCallback, useMemo, useState } from 'react'
import { toast } from 'sonner'
export const useExportSchemaToImage = () => {
const [isDownloading, setIsDownloading] = useState(false)
// By doing this once and passing the result to html-to-image options, we avoid html-to-image calculating it for every node.
// This improves performance a lot. See https://github.com/bubkoo/html-to-image/issues/542#issuecomment-3249408793
const allPropertyNames = useMemo(() => getAllPropertyNames(), [])
const exportSchemaToImage = useCallback(
async ({
element,
projectRef,
x,
y,
zoom,
format,
}: {
element: HTMLElement
projectRef: string
x: number
y: number
zoom: number
format: 'svg' | 'png'
}) => {
setIsDownloading(true)
const width = element.clientWidth
const height = element.clientHeight
const options = {
includeStyleProperties: allPropertyNames,
backgroundColor: 'white',
width,
height,
style: {
width: width.toString(),
height: height.toString(),
transform: `translate(${x}px, ${y}px) scale(${zoom})`,
},
skipFonts: true,
}
try {
if (format === 'svg') {
const data = await toSvg(element, options)
const a = document.createElement('a')
a.setAttribute('download', `supabase-schema-${projectRef}.svg`)
a.setAttribute('href', data)
a.click()
toast.success('Successfully downloaded as SVG')
} else if (format === 'png') {
const data = await toPng(element, options)
const a = document.createElement('a')
a.setAttribute('download', `supabase-schema-${projectRef}.png`)
a.setAttribute('href', data)
a.click()
toast.success('Successfully downloaded as PNG')
}
} catch (error) {
console.error('Failed to download:', error)
toast.error(`Failed to download current view: ${(error as Error).message}`)
} finally {
setIsDownloading(false)
}
},
[allPropertyNames]
)
return useMemo(
() => ({ isDownloading, exportSchemaToImage }),
[isDownloading, exportSchemaToImage]
)
}
// Get all property names accessible through getComputedStyle(), excluding custom properties
export const getAllPropertyNames = () => {
if (typeof document === 'undefined' || typeof getComputedStyle === 'undefined') {
return []
}
const names = []
const style = getComputedStyle(document.documentElement)
for (let i = 0; i < style.length; i++) {
const name = style[i]
if (!name.startsWith('--')) {
names.push(name)
}
}
return names
}