Initial OSS commit
This commit is contained in:
commit
4a7303f40a
391 changed files with 245738 additions and 0 deletions
1
packages/playground/.gitignore
vendored
Normal file
1
packages/playground/.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
/dist
|
203
packages/playground/LICENSE
Normal file
203
packages/playground/LICENSE
Normal file
|
@ -0,0 +1,203 @@
|
|||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
39
packages/playground/devEnv/servePlayground.ts
Normal file
39
packages/playground/devEnv/servePlayground.ts
Normal file
|
@ -0,0 +1,39 @@
|
|||
import path from 'path'
|
||||
import {
|
||||
convertObjectToWebpackDefinePaths,
|
||||
getEnvConfig,
|
||||
} from '../../../theatre/devEnv/webpack/createWebpackConfig'
|
||||
|
||||
const playgroundDir = path.join(__dirname, '..')
|
||||
|
||||
const envConfig = getEnvConfig(true)
|
||||
|
||||
const port = 8080
|
||||
|
||||
require('esbuild')
|
||||
.serve(
|
||||
{
|
||||
port,
|
||||
servedir: path.join(playgroundDir, 'src'),
|
||||
},
|
||||
{
|
||||
entryPoints: [path.join(playgroundDir, 'src/index.tsx')],
|
||||
target: ['firefox88'],
|
||||
loader: {'.png': 'file'},
|
||||
// outdir: '.',
|
||||
// watch: true,
|
||||
bundle: true,
|
||||
sourcemap: true,
|
||||
define: {
|
||||
global: 'window',
|
||||
'$env.isCore': false,
|
||||
...convertObjectToWebpackDefinePaths({
|
||||
process: {env: envConfig},
|
||||
$env: envConfig,
|
||||
}),
|
||||
},
|
||||
},
|
||||
)
|
||||
.then((server: unknown) => {
|
||||
console.log('serving', 'http://localhost:' + port)
|
||||
})
|
36
packages/playground/package.json
Normal file
36
packages/playground/package.json
Normal file
|
@ -0,0 +1,36 @@
|
|||
{
|
||||
"name": "playground",
|
||||
"version": "1.0.0-dev",
|
||||
"license": "Apache-2.0",
|
||||
"source": "src/index.tsx",
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
"files": [
|
||||
"dist/**/*"
|
||||
],
|
||||
"scripts": {
|
||||
"serve": "node -r esbuild-register devEnv/servePlayground.ts",
|
||||
"typecheck": "yarn run build",
|
||||
"build": "tsc --build ./tsconfig.json"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@react-three/drei": "^5.3.1",
|
||||
"@react-three/fiber": "^6.2.2",
|
||||
"@theatre/core": "workspace:*",
|
||||
"@theatre/plugin-r3f": "workspace:*",
|
||||
"@theatre/studio": "workspace:*",
|
||||
"@types/jest": "^26.0.23",
|
||||
"@types/lodash-es": "^4.17.4",
|
||||
"@types/node": "^15.6.2",
|
||||
"@types/react": "^17.0.9",
|
||||
"esbuild": "^0.12.5",
|
||||
"esbuild-register": "^2.5.0",
|
||||
"three": "^0.129.0",
|
||||
"typescript": "^4.3.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"autoprefixer": "^10.2.6",
|
||||
"esbuild-plugin-postcss2": "^0.0.9",
|
||||
"tailwindcss": "^2.1.2"
|
||||
}
|
||||
}
|
132
packages/playground/src/dom/Scene.tsx
Normal file
132
packages/playground/src/dom/Scene.tsx
Normal file
|
@ -0,0 +1,132 @@
|
|||
import studio from '@theatre/studio'
|
||||
import type {UseDragOpts} from '@theatre/studio/uiComponents/useDrag'
|
||||
import useDrag from '@theatre/studio/uiComponents/useDrag'
|
||||
import React, {useLayoutEffect, useMemo, useState} from 'react'
|
||||
import type {IProject, ISheet, ISheetObject} from '@theatre/core'
|
||||
import {types as t} from '@theatre/core'
|
||||
import type {IScrub, IStudio} from '@theatre/studio'
|
||||
|
||||
const boxObjectConfig = {
|
||||
props: t.compound({
|
||||
x: t.number(0),
|
||||
y: t.number(0),
|
||||
}),
|
||||
}
|
||||
|
||||
const Box: React.FC<{
|
||||
id: string
|
||||
sheet: ISheet
|
||||
selection: ISheetObject[]
|
||||
}> = ({id, sheet, selection}) => {
|
||||
// This is cheap to call and always returns the same value, so no need for useMemo()
|
||||
const obj = sheet.object(id, null, boxObjectConfig)
|
||||
|
||||
const isSelected = selection.includes(obj)
|
||||
|
||||
const [pos, setPos] = useState<{x: number; y: number}>({x: 0, y: 0})
|
||||
|
||||
useLayoutEffect(() => {
|
||||
const unsubscribeFromChanges = obj.onValuesChange((newValues) => {
|
||||
setPos(newValues)
|
||||
})
|
||||
return unsubscribeFromChanges
|
||||
}, [id])
|
||||
|
||||
const [divRef, setDivRef] = useState<HTMLElement | null>(null)
|
||||
|
||||
const dragOpts = useMemo((): UseDragOpts => {
|
||||
let scrub: IScrub | undefined
|
||||
let initial: typeof obj.value
|
||||
let firstOnDragCalled = false
|
||||
return {
|
||||
onDragStart() {
|
||||
scrub = studio.scrub()
|
||||
initial = obj.value
|
||||
firstOnDragCalled = false
|
||||
},
|
||||
onDrag(x, y) {
|
||||
if (!firstOnDragCalled) {
|
||||
studio.__experimental_setSelection([obj])
|
||||
firstOnDragCalled = true
|
||||
}
|
||||
scrub!.capture(({set}) => {
|
||||
set(obj.props, {x: x + initial.x, y: y + initial.y})
|
||||
})
|
||||
},
|
||||
onDragEnd(dragHappened) {
|
||||
if (dragHappened) {
|
||||
scrub!.commit()
|
||||
} else {
|
||||
scrub!.discard()
|
||||
}
|
||||
},
|
||||
lockCursorTo: 'move',
|
||||
}
|
||||
}, [])
|
||||
|
||||
useDrag(divRef, dragOpts)
|
||||
|
||||
return (
|
||||
<div
|
||||
onClick={() => {
|
||||
studio.__experimental_setSelection([obj])
|
||||
}}
|
||||
ref={setDivRef}
|
||||
style={{
|
||||
width: 100,
|
||||
height: 100,
|
||||
background: 'gray',
|
||||
position: 'absolute',
|
||||
left: pos.x + 'px',
|
||||
top: pos.y + 'px',
|
||||
boxSizing: 'border-box',
|
||||
border: isSelected ? '1px solid #5a92fa' : '1px solid transparent',
|
||||
}}
|
||||
></div>
|
||||
)
|
||||
}
|
||||
|
||||
let lastBoxId = 1
|
||||
|
||||
export const Scene: React.FC<{project: IProject}> = ({project}) => {
|
||||
const [boxes, setBoxes] = useState<Array<string>>(['0', '1'])
|
||||
|
||||
// This is cheap to call and always returns the same value, so no need for useMemo()
|
||||
const sheet = project.sheet('Scene', 'default')
|
||||
const [selection, setSelection] = useState<IStudio['selection']>()
|
||||
|
||||
useLayoutEffect(() => {
|
||||
return studio.__experimental_onSelectionChange((newState) => {
|
||||
setSelection(newState)
|
||||
})
|
||||
})
|
||||
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
position: 'absolute',
|
||||
left: '20vw',
|
||||
right: '20vw',
|
||||
top: 0,
|
||||
bottom: '30vh',
|
||||
background: 'black',
|
||||
}}
|
||||
>
|
||||
<button
|
||||
onClick={() => {
|
||||
setBoxes((boxes) => [...boxes, String(++lastBoxId)])
|
||||
}}
|
||||
>
|
||||
Add
|
||||
</button>
|
||||
{boxes.map((id) => (
|
||||
<Box
|
||||
key={'box' + id}
|
||||
id={id}
|
||||
sheet={sheet}
|
||||
selection={selection ?? []}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
12
packages/playground/src/dom/index.tsx
Normal file
12
packages/playground/src/dom/index.tsx
Normal file
|
@ -0,0 +1,12 @@
|
|||
import React from 'react'
|
||||
import ReactDOM from 'react-dom'
|
||||
import studio from '@theatre/studio'
|
||||
import {getProject} from '@theatre/core'
|
||||
import {Scene} from './Scene'
|
||||
|
||||
studio.ui
|
||||
|
||||
ReactDOM.render(
|
||||
<Scene project={getProject('Sample project')} />,
|
||||
document.getElementById('root'),
|
||||
)
|
19
packages/playground/src/index.html
Normal file
19
packages/playground/src/index.html
Normal file
|
@ -0,0 +1,19 @@
|
|||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>Playground – Theatre.js</title>
|
||||
<style>
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
|
||||
<script src="./index.js"></script>
|
||||
</body>
|
||||
</html>
|
1
packages/playground/src/index.tsx
Normal file
1
packages/playground/src/index.tsx
Normal file
|
@ -0,0 +1 @@
|
|||
import './r3f'
|
3
packages/playground/src/playground-globals.d.ts
vendored
Normal file
3
packages/playground/src/playground-globals.d.ts
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
declare module '*.png' {
|
||||
export default string
|
||||
}
|
157
packages/playground/src/r3f/App.tsx
Normal file
157
packages/playground/src/r3f/App.tsx
Normal file
|
@ -0,0 +1,157 @@
|
|||
import {editable as e, configure} from '@theatre/plugin-r3f'
|
||||
import {PerspectiveCamera} from '@react-three/drei'
|
||||
import {getProject} from '@theatre/core'
|
||||
import * as THREE from 'three'
|
||||
import React, {useState, useEffect, useRef} from 'react'
|
||||
import type {Color} from '@react-three/fiber'
|
||||
import {Canvas, useFrame} from '@react-three/fiber'
|
||||
import {softShadows, Shadow} from '@react-three/drei'
|
||||
import type {DirectionalLight} from 'three'
|
||||
import type {$FixMe} from '../../../plugin-r3f/src/types'
|
||||
|
||||
const ECamera = e(PerspectiveCamera, 'perspectiveCamera')
|
||||
|
||||
const bindToCanvas = configure({})
|
||||
|
||||
// Soft shadows are expensive, comment and refresh when it's too slow
|
||||
softShadows()
|
||||
|
||||
// credit: https://codesandbox.io/s/camera-pan-nsb7f
|
||||
|
||||
function Button() {
|
||||
const vec = new THREE.Vector3()
|
||||
const light = useRef<DirectionalLight>(undefined as any)
|
||||
const [active, setActive] = useState(false)
|
||||
const [zoom, set] = useState(true)
|
||||
useEffect(
|
||||
() => void (document.body.style.cursor = active ? 'pointer' : 'auto'),
|
||||
[active],
|
||||
)
|
||||
|
||||
useFrame((state) => {
|
||||
const step = 0.1
|
||||
const camera = state.camera as THREE.PerspectiveCamera
|
||||
camera.fov = (THREE as $FixMe).MathUtils.lerp(
|
||||
camera.fov,
|
||||
zoom ? 10 : 42,
|
||||
step,
|
||||
)
|
||||
camera.position.lerp(
|
||||
vec.set(zoom ? 25 : 10, zoom ? 1 : 5, zoom ? 0 : 10),
|
||||
step,
|
||||
)
|
||||
state.camera.lookAt(0, 0, 0)
|
||||
state.camera.updateProjectionMatrix()
|
||||
|
||||
light.current.position.lerp(
|
||||
vec.set(zoom ? 4 : 0, zoom ? 3 : 8, zoom ? 3 : 5),
|
||||
step,
|
||||
)
|
||||
})
|
||||
|
||||
return (
|
||||
<e.mesh
|
||||
receiveShadow
|
||||
castShadow
|
||||
onClick={() => set(!zoom)}
|
||||
onPointerOver={() => setActive(true)}
|
||||
onPointerOut={() => setActive(false)}
|
||||
uniqueName="The Button"
|
||||
>
|
||||
<sphereBufferGeometry args={[0.75, 64, 64]} />
|
||||
<meshPhysicalMaterial
|
||||
color={active ? 'purple' : '#e7b056'}
|
||||
clearcoat={1}
|
||||
clearcoatRoughness={0}
|
||||
/>
|
||||
<Shadow
|
||||
position-y={-0.79}
|
||||
rotation-x={-Math.PI / 2}
|
||||
opacity={0.6}
|
||||
scale={[0.8, 0.8, 1]}
|
||||
/>
|
||||
<directionalLight
|
||||
ref={light}
|
||||
castShadow
|
||||
intensity={1.5}
|
||||
shadow-camera-far={70}
|
||||
/>
|
||||
</e.mesh>
|
||||
)
|
||||
}
|
||||
|
||||
function Plane({
|
||||
color,
|
||||
uniqueName,
|
||||
...props
|
||||
}: {color: Color; uniqueName: string} & Parameters<typeof e.mesh>[0]) {
|
||||
return (
|
||||
<e.mesh receiveShadow castShadow {...props} uniqueName={uniqueName}>
|
||||
<boxBufferGeometry />
|
||||
<meshStandardMaterial color={color as any} />
|
||||
</e.mesh>
|
||||
)
|
||||
}
|
||||
|
||||
function App() {
|
||||
return (
|
||||
<div>
|
||||
<Canvas
|
||||
// @ts-ignore
|
||||
shadowMap
|
||||
onCreated={bindToCanvas({
|
||||
sheet: getProject('Example project').sheet('R3F-Canvas'),
|
||||
})}
|
||||
>
|
||||
<ECamera makeDefault uniqueName="Camera" />
|
||||
<ambientLight intensity={0.4} />
|
||||
<e.pointLight
|
||||
position={[-10, -10, 5]}
|
||||
intensity={2}
|
||||
color="#ff20f0"
|
||||
uniqueName="Light 1"
|
||||
/>
|
||||
<e.pointLight
|
||||
position={[0, 0.5, -1]}
|
||||
distance={1}
|
||||
intensity={2}
|
||||
color="#e4be00"
|
||||
uniqueName="Light 2"
|
||||
/>
|
||||
<group position={[0, -0.9, -3]}>
|
||||
<Plane
|
||||
color="hotpink"
|
||||
rotation-x={-Math.PI / 2}
|
||||
position-z={2}
|
||||
scale={[4, 20, 0.2]}
|
||||
uniqueName="plane1"
|
||||
/>
|
||||
<Plane
|
||||
color="#e4be00"
|
||||
rotation-x={-Math.PI / 2}
|
||||
position-y={1}
|
||||
scale={[4.2, 0.2, 4]}
|
||||
uniqueName="plane2"
|
||||
/>
|
||||
<Plane
|
||||
color="#736fbd"
|
||||
rotation-x={-Math.PI / 2}
|
||||
position={[-1.7, 1, 3.5]}
|
||||
scale={[0.5, 4, 4]}
|
||||
uniqueName="plane3"
|
||||
/>
|
||||
<Plane
|
||||
color="white"
|
||||
rotation-x={-Math.PI / 2}
|
||||
position={[0, 4.5, 3]}
|
||||
scale={[2, 0.03, 4]}
|
||||
uniqueName="plane4"
|
||||
/>
|
||||
</group>
|
||||
<Button />
|
||||
</Canvas>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default App
|
16
packages/playground/src/r3f/index.css
Normal file
16
packages/playground/src/r3f/index.css
Normal file
|
@ -0,0 +1,16 @@
|
|||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
html,
|
||||
body,
|
||||
#root {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
body {
|
||||
background: white;
|
||||
}
|
6
packages/playground/src/r3f/index.tsx
Normal file
6
packages/playground/src/r3f/index.tsx
Normal file
|
@ -0,0 +1,6 @@
|
|||
import React from 'react'
|
||||
import ReactDOM from 'react-dom'
|
||||
import './index.css'
|
||||
import App from './App'
|
||||
|
||||
ReactDOM.render(<App />, document.getElementById('root'))
|
150
packages/playground/src/redesign/Scene.tsx
Normal file
150
packages/playground/src/redesign/Scene.tsx
Normal file
|
@ -0,0 +1,150 @@
|
|||
import type {UseDragOpts} from '@theatre/studio/uiComponents/useDrag'
|
||||
import useDrag from '@theatre/studio/uiComponents/useDrag'
|
||||
import React, {useLayoutEffect, useMemo, useState} from 'react'
|
||||
import studio from '@theatre/studio'
|
||||
import type {IProject, ISheet, ISheetObject} from '@theatre/core'
|
||||
import {types as t} from '@theatre/core'
|
||||
import type {IScrub, IStudio} from '@theatre/studio'
|
||||
|
||||
const boxObjectConfig = {
|
||||
props: t.compound({
|
||||
position: t.compound({
|
||||
x: t.number(0),
|
||||
y: t.number(0),
|
||||
z: t.number(0),
|
||||
}),
|
||||
scale: t.compound({
|
||||
x: t.number(0),
|
||||
y: t.number(0),
|
||||
z: t.number(0),
|
||||
origin: t.compound({
|
||||
x: t.number(0),
|
||||
y: t.number(0),
|
||||
}),
|
||||
w: t.number(0),
|
||||
}),
|
||||
}),
|
||||
}
|
||||
|
||||
const Box: React.FC<{
|
||||
id: string
|
||||
sheet: ISheet
|
||||
selection: ISheetObject[]
|
||||
}> = ({id, sheet, selection: selection}) => {
|
||||
// This is cheap to call and always returns the same value, so no need for useMemo()
|
||||
const obj = sheet.object('object ' + id, null, boxObjectConfig)
|
||||
|
||||
const isSelected = selection.includes(obj)
|
||||
|
||||
const [pos, setPos] = useState<{x: number; y: number}>({x: 0, y: 0})
|
||||
|
||||
useLayoutEffect(() => {
|
||||
const unsubscribeFromChanges = obj.onValuesChange((newValues) => {
|
||||
setPos(newValues.position)
|
||||
})
|
||||
return unsubscribeFromChanges
|
||||
}, [id])
|
||||
|
||||
const [divRef, setDivRef] = useState<HTMLElement | null>(null)
|
||||
|
||||
const dragOpts = useMemo((): UseDragOpts => {
|
||||
let scrub: IScrub | undefined
|
||||
let initial: typeof obj.value.position
|
||||
let firstOnDragCalled = false
|
||||
return {
|
||||
onDragStart() {
|
||||
scrub = studio.scrub()
|
||||
initial = obj.value.position
|
||||
firstOnDragCalled = false
|
||||
},
|
||||
onDrag(x, y) {
|
||||
if (!firstOnDragCalled) {
|
||||
studio.__experimental_setSelection([obj])
|
||||
firstOnDragCalled = true
|
||||
}
|
||||
scrub!.capture(({set}) => {
|
||||
set(obj.props.position, {
|
||||
x: x + initial.x,
|
||||
y: y + initial.y,
|
||||
z: 0,
|
||||
})
|
||||
})
|
||||
},
|
||||
onDragEnd(dragHappened) {
|
||||
if (dragHappened) {
|
||||
scrub!.commit()
|
||||
} else {
|
||||
scrub!.discard()
|
||||
}
|
||||
},
|
||||
lockCursorTo: 'move',
|
||||
}
|
||||
}, [])
|
||||
|
||||
useDrag(divRef, dragOpts)
|
||||
|
||||
return (
|
||||
<div
|
||||
onClick={() => {
|
||||
studio.__experimental_setSelection([obj])
|
||||
}}
|
||||
ref={setDivRef}
|
||||
style={{
|
||||
width: 100,
|
||||
height: 100,
|
||||
background: 'gray',
|
||||
position: 'absolute',
|
||||
left: pos.x + 'px',
|
||||
top: pos.y + 'px',
|
||||
boxSizing: 'border-box',
|
||||
border: isSelected ? '1px solid #5a92fa' : '1px solid transparent',
|
||||
}}
|
||||
></div>
|
||||
)
|
||||
}
|
||||
|
||||
let lastBoxId = 1
|
||||
|
||||
export const Scene: React.FC<{project: IProject}> = ({project}) => {
|
||||
const [boxes, setBoxes] = useState<Array<string>>(['0', '1'])
|
||||
|
||||
// This is cheap to call and always returns the same value, so no need for useMemo()
|
||||
const sheet = project.sheet('Scene', 'default')
|
||||
const [selection, setSelection] = useState<IStudio['selection']>()
|
||||
|
||||
useLayoutEffect(() => {
|
||||
return studio.__experimental_onSelectionChange((newState) => {
|
||||
setSelection(newState)
|
||||
})
|
||||
})
|
||||
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
position: 'absolute',
|
||||
left: '20vw',
|
||||
right: '20vw',
|
||||
top: 0,
|
||||
bottom: '30vh',
|
||||
background: 'black',
|
||||
display: 'none',
|
||||
}}
|
||||
>
|
||||
<button
|
||||
onClick={() => {
|
||||
setBoxes((boxes) => [...boxes, String(++lastBoxId)])
|
||||
}}
|
||||
>
|
||||
Add
|
||||
</button>
|
||||
{boxes.map((id) => (
|
||||
<Box
|
||||
key={'box' + id}
|
||||
id={id}
|
||||
sheet={sheet}
|
||||
selection={selection ?? []}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
33
packages/playground/src/redesign/index.tsx
Normal file
33
packages/playground/src/redesign/index.tsx
Normal file
|
@ -0,0 +1,33 @@
|
|||
import React from 'react'
|
||||
import ReactDOM from 'react-dom'
|
||||
import studio from '@theatre/studio'
|
||||
import {getProject} from '@theatre/core'
|
||||
import {Scene} from './Scene'
|
||||
import bg from '../../xeno/bgs/8.png'
|
||||
|
||||
studio.ui
|
||||
|
||||
document.body.style.cssText = `
|
||||
background-image: url(${bg});
|
||||
background-color: #3A3A39;
|
||||
background-position: center bottom;
|
||||
background-size: calc(100% - 0px);
|
||||
background-repeat: no-repeat;
|
||||
`
|
||||
;(function renderDragArea() {
|
||||
const div = document.createElement('div')
|
||||
div.style.cssText = `
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 80px;
|
||||
height: 20px;
|
||||
-webkit-app-region: drag;
|
||||
`
|
||||
document.body.appendChild(div)
|
||||
})()
|
||||
|
||||
ReactDOM.render(
|
||||
<Scene project={getProject('Sample project')} />,
|
||||
document.getElementById('root'),
|
||||
)
|
152
packages/playground/src/turtle/TurtleRenderer.tsx
Normal file
152
packages/playground/src/turtle/TurtleRenderer.tsx
Normal file
|
@ -0,0 +1,152 @@
|
|||
import type {MutableRefObject} from 'react'
|
||||
import React, {
|
||||
useCallback,
|
||||
useEffect,
|
||||
useLayoutEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState,
|
||||
} from 'react'
|
||||
import studio from '@theatre/studio'
|
||||
import type {ISheet} from '@theatre/core'
|
||||
import {types} from '@theatre/core'
|
||||
import type {ITurtle} from './turtle'
|
||||
import {drawTurtlePlan, makeTurtlePlan} from './turtle'
|
||||
|
||||
const objConfig = {
|
||||
props: types.compound({
|
||||
startingPoint: types.compound({
|
||||
x: types.number(0.5, {min: 0, max: 1}),
|
||||
y: types.number(0.5, {min: 0, max: 1}),
|
||||
}),
|
||||
scale: types.number(1, {min: 0.1}),
|
||||
}),
|
||||
}
|
||||
|
||||
const TurtleRenderer: React.FC<{
|
||||
sheet: ISheet
|
||||
objKey: string
|
||||
width: number
|
||||
height: number
|
||||
programFn: (t: ITurtle) => void
|
||||
}> = (props) => {
|
||||
const [canvas, setCanvas] = useState<HTMLCanvasElement | null>(null)
|
||||
|
||||
const context = useMemo(() => {
|
||||
if (canvas) {
|
||||
return canvas.getContext('2d')!
|
||||
}
|
||||
}, [canvas])
|
||||
|
||||
const dimsRef = useRef({width: props.width, height: props.height})
|
||||
dimsRef.current = {width: props.width, height: props.height}
|
||||
|
||||
const obj = useMemo(() => {
|
||||
return props.sheet.object(props.objKey, null, objConfig)
|
||||
}, [props.sheet, props.objKey])
|
||||
|
||||
useEffect(() => {
|
||||
obj.onValuesChange((v) => {
|
||||
setTransforms(v)
|
||||
})
|
||||
}, [obj])
|
||||
|
||||
const [transforms, transformsRef, setTransforms] = useStateAndRef<
|
||||
typeof obj.value
|
||||
>({scale: 1, startingPoint: {x: 0.5, y: 0.5}})
|
||||
|
||||
const bounds = useMemo(() => canvas?.getBoundingClientRect(), [canvas])
|
||||
|
||||
useLayoutEffect(() => {
|
||||
if (!canvas) return
|
||||
|
||||
const receiveWheelEvent = (event: WheelEvent) => {
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
const oldTransform = transformsRef.current
|
||||
const newTransform: typeof oldTransform = {
|
||||
...oldTransform,
|
||||
startingPoint: {...oldTransform.startingPoint},
|
||||
}
|
||||
|
||||
if (event.ctrlKey) {
|
||||
const scaleFactor = 1 - (event.deltaY / dimsRef.current.height) * 1.2
|
||||
newTransform.scale *= scaleFactor
|
||||
|
||||
// const bounds = canvas.getBoundingClientRect()
|
||||
|
||||
const anchorPoint = {
|
||||
x: (event.clientX - bounds!.left) / dimsRef.current.width,
|
||||
y: (event.clientY - bounds!.top) / dimsRef.current.height,
|
||||
}
|
||||
|
||||
newTransform.startingPoint.x =
|
||||
anchorPoint.x -
|
||||
(anchorPoint.x - newTransform.startingPoint.x) * scaleFactor
|
||||
|
||||
newTransform.startingPoint.y =
|
||||
anchorPoint.y -
|
||||
(anchorPoint.y - newTransform.startingPoint.y) * scaleFactor
|
||||
} else {
|
||||
newTransform.startingPoint.x =
|
||||
oldTransform.startingPoint.x - event.deltaX / dimsRef.current.width
|
||||
newTransform.startingPoint.y =
|
||||
oldTransform.startingPoint.y - event.deltaY / dimsRef.current.height
|
||||
}
|
||||
studio.transaction((api) => {
|
||||
api.set(obj.props, newTransform)
|
||||
})
|
||||
// setTransforms(newTransform)
|
||||
}
|
||||
|
||||
const listenerOptions = {
|
||||
capture: true,
|
||||
passive: false,
|
||||
}
|
||||
canvas.addEventListener('wheel', receiveWheelEvent, listenerOptions)
|
||||
|
||||
return () => {
|
||||
canvas.removeEventListener('wheel', receiveWheelEvent, listenerOptions)
|
||||
}
|
||||
}, [canvas])
|
||||
|
||||
const plan = useMemo(() => makeTurtlePlan(props.programFn), [props.programFn])
|
||||
|
||||
useEffect(() => {
|
||||
if (!context) return
|
||||
|
||||
drawTurtlePlan(
|
||||
plan,
|
||||
context,
|
||||
{
|
||||
width: props.width,
|
||||
height: props.height,
|
||||
scale: transforms.scale,
|
||||
startFrom: {
|
||||
x: transforms.startingPoint.x * props.width,
|
||||
y: transforms.startingPoint.y * props.height,
|
||||
},
|
||||
},
|
||||
1,
|
||||
)
|
||||
}, [props.width, props.height, plan, context, transforms])
|
||||
|
||||
return (
|
||||
<canvas width={props.width} height={props.height} ref={setCanvas}></canvas>
|
||||
)
|
||||
}
|
||||
|
||||
function useStateAndRef<S>(
|
||||
initial: S,
|
||||
): [S, MutableRefObject<S>, (s: S) => void] {
|
||||
const [state, setState] = useState(initial)
|
||||
const stateRef = useRef(state)
|
||||
const set = useCallback((s: S) => {
|
||||
stateRef.current = s
|
||||
setState(s)
|
||||
}, [])
|
||||
|
||||
return [state, stateRef, set]
|
||||
}
|
||||
|
||||
export default TurtleRenderer
|
52
packages/playground/src/turtle/index.tsx
Normal file
52
packages/playground/src/turtle/index.tsx
Normal file
|
@ -0,0 +1,52 @@
|
|||
// sdf
|
||||
import React, {useMemo, useState} from 'react'
|
||||
import {render} from 'react-dom'
|
||||
import {getProject} from '@theatre/core'
|
||||
import type {ITurtle} from './turtle'
|
||||
import TurtleRenderer from './TurtleRenderer'
|
||||
import {useBoundingClientRect} from './utils'
|
||||
|
||||
const project = getProject('Turtle Playground')
|
||||
|
||||
const sheet = project.sheet('Turtle', 'The only one')
|
||||
|
||||
const TurtleExample: React.FC<{}> = (props) => {
|
||||
const [container, setContainer] = useState<HTMLDivElement | null>(null)
|
||||
const programFn = useMemo(() => {
|
||||
return ({forward, backward, left, right, repeat}: ITurtle) => {
|
||||
const steps = 10
|
||||
repeat(steps, () => {
|
||||
forward(steps * 2)
|
||||
right(360 / steps)
|
||||
})
|
||||
}
|
||||
}, [])
|
||||
|
||||
const bounds = useBoundingClientRect(container)
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={setContainer}
|
||||
style={{
|
||||
position: 'fixed',
|
||||
top: '0',
|
||||
right: '20vw',
|
||||
bottom: '30vh',
|
||||
left: '20vw',
|
||||
background: 'black',
|
||||
}}
|
||||
>
|
||||
{bounds && (
|
||||
<TurtleRenderer
|
||||
sheet={sheet}
|
||||
objKey="Renderer"
|
||||
width={bounds.width}
|
||||
height={bounds.height}
|
||||
programFn={programFn}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
render(<TurtleExample />, document.getElementById('root'))
|
189
packages/playground/src/turtle/turtle.ts
Normal file
189
packages/playground/src/turtle/turtle.ts
Normal file
|
@ -0,0 +1,189 @@
|
|||
import clamp from 'lodash-es/clamp'
|
||||
|
||||
type Op_Move = {
|
||||
type: 'Move'
|
||||
amount: number
|
||||
angle: number
|
||||
penDown: boolean
|
||||
}
|
||||
|
||||
type Op_ModifyContext = {
|
||||
type: 'ContextModifier'
|
||||
fn: (ctx: CanvasRenderingContext2D) => void
|
||||
}
|
||||
|
||||
type Op = Op_ModifyContext | Op_Move
|
||||
|
||||
type IPlan = {
|
||||
totalTravel: number
|
||||
ops: Op[]
|
||||
}
|
||||
|
||||
export function makeTurtlePlan(fn: (turtle: Turtle) => void): IPlan {
|
||||
const plan: IPlan = {
|
||||
totalTravel: 0,
|
||||
ops: [],
|
||||
}
|
||||
|
||||
const turtle = new Turtle(plan)
|
||||
fn(turtle)
|
||||
return plan
|
||||
}
|
||||
|
||||
export function drawTurtlePlan(
|
||||
plan: IPlan,
|
||||
ctx: CanvasRenderingContext2D,
|
||||
{
|
||||
width,
|
||||
height,
|
||||
scale,
|
||||
startFrom,
|
||||
}: {
|
||||
width: number
|
||||
height: number
|
||||
scale: number
|
||||
startFrom: {x: number; y: number}
|
||||
},
|
||||
tilProgression: number,
|
||||
): void {
|
||||
const {ops} = plan
|
||||
if (ops.length === 0) return
|
||||
|
||||
const targetDistance = clamp(tilProgression, 0, 1) * plan.totalTravel
|
||||
|
||||
ctx.clearRect(0, 0, width, height)
|
||||
|
||||
let traveledSoFar = 0
|
||||
let pos = {...startFrom}
|
||||
ctx.beginPath()
|
||||
ctx.lineWidth = 2
|
||||
ctx.strokeStyle = 'white'
|
||||
ctx.moveTo(pos.x, pos.y)
|
||||
|
||||
for (const op of ops) {
|
||||
if (traveledSoFar >= targetDistance) return
|
||||
|
||||
if (op.type === 'ContextModifier') {
|
||||
op.fn(ctx)
|
||||
} else {
|
||||
let amount = Math.abs(op.amount)
|
||||
const sign = op.amount < 0 ? -1 : 1
|
||||
const {angle} = op
|
||||
|
||||
const roomTilTarget = targetDistance - traveledSoFar
|
||||
|
||||
const distanceInThisStep = roomTilTarget < amount ? roomTilTarget : amount
|
||||
|
||||
traveledSoFar += distanceInThisStep
|
||||
|
||||
pos = move(pos, angle, distanceInThisStep * sign, scale, op.penDown, ctx)
|
||||
ctx.stroke()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function move(
|
||||
pointA: {x: number; y: number},
|
||||
_angle: number,
|
||||
amount: number,
|
||||
scale: number,
|
||||
penIsDown: boolean,
|
||||
ctx: CanvasRenderingContext2D,
|
||||
): {x: number; y: number} {
|
||||
const angle = (_angle * Math.PI) / 180
|
||||
|
||||
const unrotatedTarget = {
|
||||
x: pointA.x + amount * scale,
|
||||
y: pointA.y,
|
||||
}
|
||||
|
||||
const pointB = {
|
||||
x:
|
||||
pointA.x +
|
||||
Math.cos(angle) * (unrotatedTarget.x - pointA.x) -
|
||||
Math.sin(angle) * (unrotatedTarget.y - pointA.y),
|
||||
y:
|
||||
pointA.y +
|
||||
Math.sin(angle) * (unrotatedTarget.x - pointA.x) -
|
||||
Math.sin(angle) * (unrotatedTarget.y - pointA.y),
|
||||
}
|
||||
|
||||
if (penIsDown) {
|
||||
ctx.lineTo(pointB.x, pointB.y)
|
||||
} else {
|
||||
ctx.moveTo(pointB.x, pointB.y)
|
||||
}
|
||||
|
||||
return pointB
|
||||
}
|
||||
|
||||
class Turtle {
|
||||
private _state = {
|
||||
penIsDown: true,
|
||||
angle: -90,
|
||||
}
|
||||
|
||||
constructor(private _plan: IPlan) {}
|
||||
|
||||
fn = (innerFn: () => void) => {
|
||||
return innerFn
|
||||
}
|
||||
|
||||
private _pushContextModifier(fn: (ctx: CanvasRenderingContext2D) => void) {
|
||||
this._plan.ops.push({type: 'ContextModifier', fn})
|
||||
}
|
||||
|
||||
press = (n: number) => {
|
||||
this._pushContextModifier((ctx) => {
|
||||
ctx.lineWidth = n
|
||||
})
|
||||
}
|
||||
|
||||
forward = (amount: number) => {
|
||||
this._plan.ops.push({
|
||||
type: 'Move',
|
||||
amount,
|
||||
penDown: this._state.penIsDown,
|
||||
angle: this._state.angle,
|
||||
})
|
||||
this._plan.totalTravel += Math.abs(amount)
|
||||
return this
|
||||
}
|
||||
|
||||
backward = (amount: number) => {
|
||||
return this.forward(amount)
|
||||
}
|
||||
|
||||
right = (deg: number) => {
|
||||
this._rotate(deg)
|
||||
return this
|
||||
}
|
||||
|
||||
left = (deg: number) => {
|
||||
this._rotate(-deg)
|
||||
return this
|
||||
}
|
||||
|
||||
private _rotate(deg: number) {
|
||||
this._state.angle += deg
|
||||
}
|
||||
|
||||
penup = () => {
|
||||
this._state.penIsDown = false
|
||||
return this
|
||||
}
|
||||
|
||||
pendown = () => {
|
||||
this._state.penIsDown = true
|
||||
return this
|
||||
}
|
||||
|
||||
repeat = (n: number, fn: (i: number) => void) => {
|
||||
for (let i = 0; i < n; i++) {
|
||||
fn(i)
|
||||
}
|
||||
return this
|
||||
}
|
||||
}
|
||||
|
||||
export type ITurtle = Turtle
|
19
packages/playground/src/turtle/utils.ts
Normal file
19
packages/playground/src/turtle/utils.ts
Normal file
|
@ -0,0 +1,19 @@
|
|||
import {useLayoutEffect, useState} from 'react'
|
||||
|
||||
export function useBoundingClientRect(
|
||||
node: HTMLElement | null,
|
||||
): null | DOMRect {
|
||||
const [bounds, set] = useState<null | DOMRect>(null)
|
||||
|
||||
useLayoutEffect(() => {
|
||||
if (node) {
|
||||
set(node.getBoundingClientRect())
|
||||
}
|
||||
|
||||
return () => {
|
||||
set(null)
|
||||
}
|
||||
}, [node])
|
||||
|
||||
return bounds
|
||||
}
|
17
packages/playground/tsconfig.json
Normal file
17
packages/playground/tsconfig.json
Normal file
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "dist",
|
||||
"lib": ["ESNext", "DOM"],
|
||||
"rootDir": ".",
|
||||
"types": ["jest", "node"],
|
||||
"emitDeclarationOnly": false,
|
||||
"composite": true
|
||||
},
|
||||
"references": [
|
||||
{"path": "../../theatre"},
|
||||
{"path": "../dataverse"},
|
||||
{"path": "../plugin-r3f"}
|
||||
],
|
||||
"include": ["./src/**/*", "./devEnv/**/*"]
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue