@pascal-app/editor 1.1.1 → 1.1.3

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pascal-app/editor",
3
- "version": "1.1.1",
3
+ "version": "1.1.3",
4
4
  "description": "Pascal building editor component",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -17,8 +17,8 @@
17
17
  "test": "bun test src"
18
18
  },
19
19
  "peerDependencies": {
20
- "@pascal-app/core": "^1.1.1",
21
- "@pascal-app/viewer": "^1.1.1",
20
+ "@pascal-app/core": "^1.1.3",
21
+ "@pascal-app/viewer": "^1.1.3",
22
22
  "@react-three/drei": "^10",
23
23
  "@react-three/fiber": "^9",
24
24
  "next": ">=15",
@@ -33,8 +33,8 @@
33
33
  "@iconify/react": "^6.0.4",
34
34
  "@lingui/core": "6.12.0",
35
35
  "@number-flow/react": "^0.6.2",
36
- "@pascal-app/geometry-script": "^1.1.1",
37
- "@pascal-app/ifc-converter": "^1.1.1",
36
+ "@pascal-app/geometry-script": "^1.1.3",
37
+ "@pascal-app/ifc-converter": "^1.1.3",
38
38
  "@pascal-app/lingo": "^0.5.0",
39
39
  "@radix-ui/react-alert-dialog": "^1.2.1",
40
40
  "@radix-ui/react-context-menu": "^2.4.1",
@@ -64,8 +64,8 @@
64
64
  "zustand": "^5.0.16"
65
65
  },
66
66
  "devDependencies": {
67
- "@pascal-app/core": "^1.1.1",
68
- "@pascal-app/viewer": "^1.1.1",
67
+ "@pascal-app/core": "^1.1.3",
68
+ "@pascal-app/viewer": "^1.1.3",
69
69
  "@pascal/typescript-config": "*",
70
70
  "@react-three/test-renderer": "^9.1.1",
71
71
  "@types/blob-stream": "^0.1.33",
@@ -0,0 +1,46 @@
1
+ import { describe, expect, test } from 'bun:test'
2
+ import { imageConfigDefault } from 'next/dist/shared/lib/image-config'
3
+ import { ImageConfigContext } from 'next/dist/shared/lib/image-config-context.shared-runtime'
4
+ import { renderToStaticMarkup } from 'react-dom/server'
5
+ import { canOptimizeImageSrc, NodeIconImage } from './node-icon-image'
6
+
7
+ const config = {
8
+ ...imageConfigDefault,
9
+ remotePatterns: [{ protocol: 'https' as const, hostname: 'assets.example.com' }],
10
+ }
11
+
12
+ const render = (src: string) =>
13
+ renderToStaticMarkup(
14
+ <ImageConfigContext.Provider value={config}>
15
+ <NodeIconImage alt="" height={16} src={src} width={16} />
16
+ </ImageConfigContext.Provider>,
17
+ )
18
+
19
+ describe('canOptimizeImageSrc', () => {
20
+ test('optimizes site-relative paths and configured hosts', () => {
21
+ expect(canOptimizeImageSrc('/icons/item.png', config)).toBe(true)
22
+ expect(canOptimizeImageSrc('https://assets.example.com/thumb.webp', config)).toBe(true)
23
+ })
24
+
25
+ test('does not optimize hosts missing from remotePatterns', () => {
26
+ expect(canOptimizeImageSrc('https://prod.supabase.co/storage/thumb.webp', config)).toBe(false)
27
+ expect(canOptimizeImageSrc('//prod.supabase.co/thumb.webp', config)).toBe(false)
28
+ })
29
+
30
+ test('keeps optimizing when the config carries no host lists, as in production builds', () => {
31
+ expect(canOptimizeImageSrc('https://prod.supabase.co/storage/thumb.webp', {})).toBe(true)
32
+ })
33
+ })
34
+
35
+ describe('NodeIconImage', () => {
36
+ test('renders an unconfigured host straight from its URL', () => {
37
+ const src = 'https://prod.supabase.co/storage/v1/object/public/items/thumb.webp'
38
+ const html = render(src)
39
+ expect(html).toContain(`src="${src}"`)
40
+ expect(html).not.toContain('/_next/image')
41
+ })
42
+
43
+ test('still optimizes a configured host', () => {
44
+ expect(render('https://assets.example.com/thumb.webp')).toContain('/_next/image?url=')
45
+ })
46
+ })
@@ -1,7 +1,35 @@
1
1
  'use client'
2
2
 
3
3
  import { ARTIFACT_URL_PREFIX, resolveArtifactUrl } from '@pascal-app/core'
4
+ import { type ImageConfigComplete, imageConfigDefault } from 'next/dist/shared/lib/image-config'
5
+ import { ImageConfigContext } from 'next/dist/shared/lib/image-config-context.shared-runtime'
6
+ import { hasRemoteMatch } from 'next/dist/shared/lib/match-remote-pattern'
4
7
  import Image, { type ImageProps } from 'next/image'
8
+ import { useContext } from 'react'
9
+
10
+ type ImageHostConfig = Partial<Pick<ImageConfigComplete, 'domains' | 'remotePatterns'>>
11
+
12
+ // Inlined by Next's bundler define, as next/image itself reads it.
13
+ const configEnv = process.env.__NEXT_IMAGE_OPTS as unknown as ImageHostConfig | undefined
14
+
15
+ /**
16
+ * Whether next/image can optimize `src`. An absolute URL on a host missing
17
+ * from `images.remotePatterns` makes next/image throw during render (e.g. a
18
+ * catalog thumbnail on production storage while running against local
19
+ * Supabase), so those render unoptimized, straight from their host.
20
+ */
21
+ export function canOptimizeImageSrc(src: string, config: ImageHostConfig): boolean {
22
+ if (src.startsWith('/') && !src.startsWith('//')) return true
23
+ // Production builds inline no host lists, and next/image validates none then.
24
+ if (!config.domains && !config.remotePatterns) return true
25
+ let url: URL
26
+ try {
27
+ url = new URL(src)
28
+ } catch {
29
+ return false
30
+ }
31
+ return hasRemoteMatch(config.domains ?? [], config.remotePatterns ?? [], url)
32
+ }
5
33
 
6
34
  /**
7
35
  * A node's icon or thumbnail. A scripted object's thumbnail is an
@@ -10,7 +38,11 @@ import Image, { type ImageProps } from 'next/image'
10
38
  * private project's artifact with.
11
39
  */
12
40
  export function NodeIconImage({ src, ...props }: Omit<ImageProps, 'src'> & { src: string }) {
13
- if (!src.startsWith(ARTIFACT_URL_PREFIX)) return <Image src={src} {...props} />
41
+ const configContext = useContext(ImageConfigContext)
42
+ const config = configEnv || configContext || imageConfigDefault
43
+ if (!src.startsWith(ARTIFACT_URL_PREFIX)) {
44
+ return <Image src={src} unoptimized={!canOptimizeImageSrc(src, config)} {...props} />
45
+ }
14
46
  const url = resolveArtifactUrl(src)
15
47
  return url ? <Image src={url} unoptimized {...props} /> : null
16
48
  }