@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.
|
|
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.
|
|
21
|
-
"@pascal-app/viewer": "^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.
|
|
37
|
-
"@pascal-app/ifc-converter": "^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.
|
|
68
|
-
"@pascal-app/viewer": "^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
|
-
|
|
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
|
}
|