Skip to content

Commit

Permalink
fix: remove assert
Browse files Browse the repository at this point in the history
feat: cache svg, support transform percentage #43, support transform in html23, insert intro video into html23
  • Loading branch information
bbohlender committed May 8, 2024
1 parent 397ddbb commit 796e327
Show file tree
Hide file tree
Showing 13 changed files with 133 additions and 64 deletions.
12 changes: 10 additions & 2 deletions apps/html23/src/components/onboarding.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,8 @@ function OnboardDialog() {
const [suggestedTutorials, setSuggestedTutorials] = useState<Array<SuggestedTutorial> | undefined>(undefined)
return (
<Dialog onOpenChange={skipOnboarding} open>
<DialogContent className="flex flex-col flex-shrink bg-black">
<div className="flex flex-col gap-6 items-center mb-8">
<DialogContent className="flex flex-col flex-shrink bg-black" style={{ maxHeight: '95dvh', overflowY: 'auto' }}>
<div className="flex flex-col gap-6 items-center mb-6">
<img width={100} src="./logo.svg" />
<h1 className="font-bold text-2xl">Welcome to HTML23</h1>
</div>
Expand All @@ -75,6 +75,14 @@ function ExperienceQuestionnaire({
return (
<>
<h2 className="text-muted-foreground">HTML23 simplifies building 3D user interfaces on the web.</h2>
<iframe
style={{ width: '100%', aspectRatio: 1.7777777 }}
src="https://www.youtube.com/embed/9mYR_WvC71Q?si=MVRXzIHzzYL4OLVI&amp;controls=0"
title="YouTube video player"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
referrerPolicy="strict-origin-when-cross-origin"
allowFullScreen
></iframe>

<h3 className="text-foreground">
For the best experience answer the following questions to the best of your knowledge.
Expand Down
2 changes: 1 addition & 1 deletion examples/uikit/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ export default function App() {
borderRightWidth={0}
borderColor="red"
>
<Portal flexShrink={0} borderRadius={30} width={200} aspectRatio={2}>
<Portal flexShrink={0} borderRadius={30} width={200} aspectRatio={1}>
<PerspectiveCamera makeDefault position={[0, 0, 4]} />
<Box rotation-y={Math.PI / 4} args={[2, 2, 2]} />
<color attach="background" args={['red']} />
Expand Down
40 changes: 21 additions & 19 deletions packages/react/src/portal.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { effect } from '@preact/signals-core'
import { Signal, computed, effect } from '@preact/signals-core'
import { ReactNode, RefAttributes, RefObject, forwardRef, useEffect, useImperativeHandle, useMemo, useRef } from 'react'
import { HalfFloatType, LinearFilter, Scene, WebGLRenderTarget } from 'three'
import { Image } from './image.js'
import { InjectState, RootState, createPortal, useFrame, useStore } from '@react-three/fiber'
import { InjectState, RootState, createPortal, useFrame, useStore, useThree } from '@react-three/fiber'
import type { DomEvent, EventHandlers } from '@react-three/fiber/dist/declarations/src/core/events.js'
import type { ImageProperties } from '@pmndrs/uikit/internals'
import type { ComponentInternals } from './ref.js'
Expand All @@ -21,16 +21,7 @@ export type PortalProperties = {
export const Portal: (props: PortalProperties & RefAttributes<ComponentInternals<PortalProperties>>) => ReactNode =
forwardRef(
({ children, resolution = 1, frames = Infinity, renderPriority = 0, eventPriority = 0, ...props }, ref) => {
// eslint-disable-next-line react-hooks/exhaustive-deps
const fbo = useMemo(
() =>
new WebGLRenderTarget(1, 1, {
minFilter: LinearFilter,
magFilter: LinearFilter,
type: HalfFloatType,
}),
[],
)
const fbo = useMemo(() => new Signal<WebGLRenderTarget | undefined>(undefined), [])
const imageRef = useRef<ComponentInternals<ImageProperties>>(null)
const injectState = useMemo<InjectState>(
() => ({
Expand All @@ -39,28 +30,35 @@ export const Portal: (props: PortalProperties & RefAttributes<ComponentInternals
}),
[eventPriority],
)
const store = useStore()
useEffect(() => {
if (imageRef.current == null) {
return
}
const renderTarget = (fbo.value = new WebGLRenderTarget(1, 1, {
minFilter: LinearFilter,
magFilter: LinearFilter,
type: HalfFloatType,
}))
const { size } = imageRef.current
const unsubscribeSetSize = effect(() => {
if (size.value == null) {
return
}
const [width, height] = size.value
fbo.setSize(width, height)
const dpr = store.getState().viewport.dpr
renderTarget.setSize(width * dpr, height * dpr)
injectState.size!.width = width
injectState.size!.height = height
})
return () => {
unsubscribeSetSize()
//TODO: portal wont work in strict mode
fbo.dispose()
renderTarget.dispose()
}
}, [fbo, injectState])
}, [fbo, injectState, store])
useImperativeHandle(ref, () => imageRef.current!, [])
const vScene = useMemo(() => new Scene(), [])
const texture = useMemo(() => computed(() => fbo.value?.texture), [fbo])
return (
<>
{createPortal(
Expand All @@ -72,7 +70,7 @@ export const Portal: (props: PortalProperties & RefAttributes<ComponentInternals
vScene,
injectState,
)}
<Image src={fbo.texture} objectFit="fill" keepAspectRatio={false} {...props} ref={imageRef} />
<Image src={texture} objectFit="fill" keepAspectRatio={false} {...props} ref={imageRef} />
</>
)
},
Expand Down Expand Up @@ -108,7 +106,7 @@ function ChildrenToFBO({
frames: number
renderPriority: number
children: ReactNode
fbo: WebGLRenderTarget
fbo: Signal<WebGLRenderTarget | undefined>
imageRef: RefObject<ComponentInternals<ImageProperties>>
}) {
const store = useStore()
Expand All @@ -129,12 +127,16 @@ function ChildrenToFBO({
let oldAutoClear
let oldXrEnabled
useFrame((state) => {
const currentFBO = fbo.peek()
if (currentFBO == null) {
return
}
if (frames === Infinity || count < frames) {
oldAutoClear = state.gl.autoClear
oldXrEnabled = state.gl.xr.enabled
state.gl.autoClear = true
state.gl.xr.enabled = false
state.gl.setRenderTarget(fbo)
state.gl.setRenderTarget(currentFBO)
state.gl.render(state.scene, state.camera)
state.gl.setRenderTarget(null)
state.gl.autoClear = oldAutoClear
Expand Down
5 changes: 4 additions & 1 deletion packages/uikit/scripts/extract-core-component-properties.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,4 +85,7 @@ const result = {
),
),
}
writeFileSync(resolve(__dirname, '../src/convert/html/properties.json'), JSON.stringify(result))
writeFileSync(
resolve(__dirname, '../src/convert/html/generated-property-types.ts'),
`export const generatedPropertyTypes = ${JSON.stringify(result)}`,
)
9 changes: 7 additions & 2 deletions packages/uikit/src/components/svg.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ import { createActivePropertyTransfomers } from '../active.js'
import { createHoverPropertyTransformers, setupCursorCleanup } from '../hover.js'
import { createInteractionPanel } from '../panel/instanced-panel-mesh.js'
import { createResponsivePropertyTransformers } from '../responsive.js'
import { SVGLoader } from 'three/examples/jsm/Addons.js'
import { SVGLoader, SVGResult } from 'three/examples/jsm/Addons.js'
import { darkPropertyTransformers } from '../dark.js'
import { PanelGroupProperties, computedPanelGroupDependencies, getDefaultPanelMaterialConfig } from '../panel/index.js'
import { KeepAspectRatioProperties } from './image.js'
Expand Down Expand Up @@ -258,6 +258,8 @@ const loader = new SVGLoader()
const box3Helper = new Box3()
const vectorHelper = new Vector3()

const svgCache = new Map<string, SVGResult>()

async function loadSvg(
url: string | undefined,
root: RootContext,
Expand All @@ -271,7 +273,10 @@ async function loadSvg(
}
const object = new Group()
object.matrixAutoUpdate = false
const result = await loader.loadAsync(url)
let result = svgCache.get(url)
if (result == null) {
svgCache.set(url, (result = await loader.loadAsync(url)))
}
box3Helper.makeEmpty()
for (const path of result.paths) {
const shapes = SVGLoader.createShapes(path)
Expand Down
2 changes: 1 addition & 1 deletion packages/uikit/src/convert/html/.gitignore
Original file line number Diff line number Diff line change
@@ -1 +1 @@
properties.json
generated-property-types.ts
15 changes: 6 additions & 9 deletions packages/uikit/src/convert/html/internals.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@ import { parse as parseHTML, Node as ConversionNode, TextNode, HTMLElement } fro
import { htmlDefaults } from './defaults.js'
import parseInlineCSS, { Declaration, Comment } from 'inline-style-parser'
import { tailwindToCSS } from 'tw-to-css'
import generatedPropertyTypes from './properties.json' assert { type: 'json' }
//@ts-ignore
import { generatedPropertyTypes } from './generated-property-types.js'
import {
ConversionColorMap,
ConversionPropertyTypes,
convertProperties as convertCssProperties,
convertProperties,
convertProperty,
isInheritingProperty,
Expand Down Expand Up @@ -454,17 +454,14 @@ function convertHtmlAttributes(
styles = parseInlineCSS(style)
}
} catch {}
const stylesMap: Record<string, string> = {}
for (const style of styles) {
if (style.type === 'comment') {
continue
}
const key = kebabToCamelCase(style.property)
const value = convertProperty(propertyTypes, key, style.value, colorMap)
if (value == null) {
continue
}
result[key] = value
stylesMap[kebabToCamelCase(style.property)] = style.value
}
Object.assign(result, convertProperties(propertyTypes, stylesMap, colorMap, kebabToCamelCase) ?? {})

if (!custom && !('display' in result) && !('flexDirection' in result)) {
const key = 'flexDirection'
Expand Down Expand Up @@ -513,7 +510,7 @@ function convertTailwind(

Object.assign(properties, tailwindToJson(withoutConditionals, classes))

return convertCssProperties(propertyTypes, properties, colorMap) ?? {}
return convertProperties(propertyTypes, properties, colorMap) ?? {}
}

export * from './properties.js'
48 changes: 38 additions & 10 deletions packages/uikit/src/convert/html/properties.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { ReadonlySignal, Signal } from '@preact/signals-core'
import { ColorRepresentation } from '../../utils.js'
import { ColorRepresentation, percentageRegex } from '../../utils.js'
import { CSSProperties } from 'react'

export type ConversionPropertyType = Array<string | Array<string>> //<- enum
Expand All @@ -23,10 +22,41 @@ const propertyRenamings = {
zIndex: 'zIndexOffset',
}

const cssShorthandPropertyTranslation: Record<
string,
(set: (key: string, value: string) => void, property: unknown) => void
> = {
const transformRegex = /(translate|rotate)(X|Y|Z|3d)?\((\s*[^,)]+\s*(?:,\s*[^,)]+\s*)*)\)/g

const customCssTranslation: Record<string, (set: (key: string, value: string) => void, property: unknown) => void> = {
transform: (set, property) => {
if (typeof property != 'string') {
return
}
let result: RegExpExecArray | null
while ((result = transformRegex.exec(property)) != null) {
const [, operation, type, values] = result
let [x, y, z] = values.split(',').map((s) => s.trim())

const prefix = `transform${operation[0].toUpperCase()}${operation.slice(1)}`

if (operation === 'rotate') {
set(`${prefix}Z`, x)
continue
}

y ??= x
z ??= x

if (type === 'X' || type === '3d' || type === undefined) {
set(`${prefix}X`, x)
}

if (type === 'Y' || type === '3d' || type === undefined) {
set(`${prefix}Y`, y)
}

if (type === 'Z' || type === '3d') {
set(`${prefix}Z`, z)
}
}
},
flex: (set, property) => {
//TODO: simplify
if (typeof property != 'string') {
Expand Down Expand Up @@ -99,8 +129,6 @@ export function isInheritingProperty(key: string): boolean {
}
}

const percentageRegex = /^(-?\d+|\d*\.\d+)\%$/

const conditionals = ['sm', 'md', 'lg', 'xl', '2xl', 'focus', 'hover', 'active', 'dark']

export function convertProperties(
Expand Down Expand Up @@ -138,8 +166,8 @@ export function convertProperties(
if (convertKey != null) {
key = convertKey(key)
}
if (key in cssShorthandPropertyTranslation) {
cssShorthandPropertyTranslation[key](set, property)
if (key in customCssTranslation) {
customCssTranslation[key](set, property)
continue
}
if (key === 'positionType' && property === 'fixed') {
Expand Down
2 changes: 1 addition & 1 deletion packages/uikit/src/panel/instanced-panel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { ColorRepresentation, Subscriptions, unsubscribeSubscriptions } from '..
import { MergedProperties } from '../properties/merged.js'
import { setupImmediateProperties } from '../properties/immediate.js'
import { OrderInfo } from '../order.js'
import { PanelMaterialConfig } from './index.js'
import { PanelMaterialConfig } from './panel-material.js'

export type PanelProperties = {
borderTopLeftRadius?: number
Expand Down
3 changes: 1 addition & 2 deletions packages/uikit/src/text/utils.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { GlyphLayout, GlyphLayoutProperties } from './layout.js'
import { Font, GlyphInfo } from './font.js'
import { percentageRegex } from '../utils.js'

export function getGlyphOffsetX(
font: Font,
Expand All @@ -11,8 +12,6 @@ export function getGlyphOffsetX(
return (kerning + glyphInfo.xoffset) * fontSize
}

const percentageRegex = /^(-?\d+(?:\.\d+)?)%$/

function lineHeightToAbsolute(lineHeight: GlyphLayoutProperties['lineHeight'], fontSize: number): number {
if (typeof lineHeight === 'number') {
return lineHeight
Expand Down
Loading

0 comments on commit 796e327

Please sign in to comment.