lunchboxjs 0.1.4018 → 0.2.1001-beta.2

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.
Files changed (41) hide show
  1. package/dist/lunchboxjs.js +1739 -1670
  2. package/dist/lunchboxjs.min.js +1 -1
  3. package/dist/lunchboxjs.module.js +1694 -1667
  4. package/extras/OrbitControlsWrapper.vue +5 -7
  5. package/package.json +15 -4
  6. package/src/components/LunchboxEventHandlers.tsx +237 -0
  7. package/src/components/LunchboxWrapper/LunchboxScene.tsx +8 -0
  8. package/src/components/LunchboxWrapper/LunchboxWrapper.tsx +341 -0
  9. package/src/components/LunchboxWrapper/prepCanvas.ts +27 -21
  10. package/src/components/LunchboxWrapper/resizeCanvas.ts +13 -12
  11. package/src/components/autoGeneratedComponents.ts +1 -1
  12. package/src/components/index.ts +2 -4
  13. package/src/core/createNode.ts +2 -18
  14. package/src/core/extend.ts +1 -1
  15. package/src/core/index.ts +0 -3
  16. package/src/core/instantiateThreeObject/index.ts +7 -2
  17. package/src/core/instantiateThreeObject/processProps.ts +1 -1
  18. package/src/core/interaction.ts +55 -0
  19. package/src/core/minidom.ts +5 -9
  20. package/src/core/update.ts +92 -53
  21. package/src/core/updateObjectProp.ts +5 -14
  22. package/src/index.ts +270 -76
  23. package/src/keys.ts +25 -0
  24. package/src/nodeOps/createElement.ts +2 -5
  25. package/src/nodeOps/index.ts +70 -57
  26. package/src/nodeOps/insert.ts +11 -32
  27. package/src/nodeOps/remove.ts +1 -17
  28. package/src/types.ts +34 -10
  29. package/src/utils/index.ts +11 -4
  30. package/dist/.DS_Store +0 -0
  31. package/src/.DS_Store +0 -0
  32. package/src/components/LunchboxWrapper/LunchboxWrapper.ts +0 -312
  33. package/src/components/catalogue.ts +0 -3
  34. package/src/core/.DS_Store +0 -0
  35. package/src/core/allNodes.ts +0 -4
  36. package/src/core/ensure.ts +0 -203
  37. package/src/core/interaction/index.ts +0 -102
  38. package/src/core/interaction/input.ts +0 -4
  39. package/src/core/interaction/interactables.ts +0 -14
  40. package/src/core/interaction/setupAutoRaycaster.ts +0 -224
  41. package/src/core/start.ts +0 -11
@@ -1,203 +0,0 @@
1
- import { allNodes, createNode, isMinidomNode, MiniDom } from '.'
2
- import { setupAutoRaycaster } from './interaction/setupAutoRaycaster'
3
- import { computed, reactive, ref, WritableComputedRef } from 'vue'
4
- import { Lunch } from '..'
5
-
6
- // ENSURE ROOT
7
- // ====================
8
- export const rootUuid = 'LUNCHBOX_ROOT'
9
- export let lunchboxRootNode: MiniDom.RendererRootNode
10
- export function ensureRootNode(options: Partial<Lunch.RootMeta> = {}) {
11
- if (!lunchboxRootNode) {
12
- lunchboxRootNode = new MiniDom.RendererRootNode(options)
13
- }
14
- return lunchboxRootNode
15
- }
16
-
17
- // This is used in `buildEnsured` below and `LunchboxWrapper`
18
- /** Search the overrides record and the node tree for a node in the given types */
19
- export function tryGetNodeWithInstanceType<T extends THREE.Object3D>(
20
- pascalCaseTypes: string | string[]
21
- ) {
22
- if (!Array.isArray(pascalCaseTypes)) {
23
- pascalCaseTypes = [pascalCaseTypes]
24
- }
25
-
26
- // default to override if we have one
27
- for (let singleType of pascalCaseTypes) {
28
- if (overrides[singleType]) return overrides[singleType] as Lunch.Node<T>
29
- }
30
-
31
- // look for auto-created node
32
- for (let singleType of pascalCaseTypes) {
33
- const found =
34
- autoCreated[singleType] ||
35
- allNodes.find(
36
- (node) =>
37
- (node as MiniDom.RendererBaseNode).type?.toLowerCase() ===
38
- singleType.toLowerCase()
39
- )
40
-
41
- // cancel if found example is marked !isDefault
42
- if (
43
- isMinidomNode(found) &&
44
- (found.props['is-default'] === false ||
45
- !found.props['isDefault'] === false)
46
- ) {
47
- return null
48
- }
49
-
50
- // if we have one, save and return
51
- if (found) {
52
- const createdAsNode = found as MiniDom.RendererStandardNode<T>
53
- autoCreated[singleType] = createdAsNode
54
- return createdAsNode
55
- }
56
- }
57
-
58
- return null
59
- }
60
-
61
- // GENERIC ENSURE FUNCTION
62
- // ====================
63
- // Problem:
64
- // I want to make sure an object of type Xyz exists in my Lunchbox app.
65
- // If it doesn't exist, I want to create it and add it to the root node.
66
- //
67
- // Solution:
68
- // export const ensuredXyz = buildEnsured<Xyz>('Xyz', 'FALLBACK_XYZ')
69
- //
70
- // Now in other components, you can do both:
71
- // import { ensuredXyz }
72
- // ensuredXyz.value (...)
73
- // and:
74
- // ensuredXyz.value = ...
75
- export const autoCreated: Record<string, Lunch.Node | null> = reactive({})
76
- export const overrides: Record<string, Lunch.Node | null> = reactive({})
77
-
78
- /**
79
- * Build a computed ensured value with a getter and setter.
80
- * @param pascalCaseTypes List of types this can be. Will autocreate first type if array provided.
81
- * @param fallbackUuid Fallback UUID to use.
82
- * @param props Props to pass to autocreated element
83
- * @returns Computed getter/setter for ensured object.
84
- */
85
- function buildEnsured<T extends THREE.Object3D>(
86
- pascalCaseTypes: string | string[],
87
- fallbackUuid: string,
88
- props: Record<string, any> = {},
89
- callback: ((node: MiniDom.RendererStandardNode<T>) => void) | null = null
90
- ) {
91
- // make sure we've got an array
92
- if (!Array.isArray(pascalCaseTypes)) {
93
- pascalCaseTypes = [pascalCaseTypes]
94
- }
95
-
96
- // add type for autoCreated and overrides
97
- for (let singleType of pascalCaseTypes) {
98
- if (!autoCreated[singleType]) {
99
- autoCreated[singleType] = null
100
- }
101
- if (!overrides[singleType]) {
102
- overrides[singleType] = null
103
- }
104
- }
105
-
106
- return computed({
107
- get(): MiniDom.RendererStandardNode<T> {
108
- // try to get existing type
109
- const existing = tryGetNodeWithInstanceType<T>(
110
- pascalCaseTypes as string[]
111
- )
112
- if (existing) return existing
113
-
114
- // otherwise, create a new node
115
- const root = ensureRootNode()
116
- const node = createNode<T>({
117
- type: pascalCaseTypes[0],
118
- uuid: fallbackUuid,
119
- props,
120
- })
121
- root.addChild(node)
122
- autoCreated[pascalCaseTypes[0]] = node
123
- if (callback) {
124
- callback(node)
125
- }
126
- return node
127
- },
128
- set(val: MiniDom.RendererStandardNode<T>) {
129
- const t = val.type ?? ''
130
- const pascalType = t[0].toUpperCase() + t.slice(1)
131
- overrides[pascalType] = val
132
- },
133
- })
134
- }
135
-
136
- // ENSURE CAMERA
137
- // ====================
138
- export const fallbackCameraUuid = 'FALLBACK_CAMERA'
139
- export const defaultCamera = buildEnsured(
140
- ['PerspectiveCamera', 'OrthographicCamera'],
141
- fallbackCameraUuid,
142
- { args: [45, 0.5625, 1, 1000] }
143
- ) as unknown as WritableComputedRef<Lunch.Node<THREE.Camera>>
144
- /** Special value to be changed ONLY in `LunchboxWrapper`.
145
- * Functions waiting for a Camera need to wait for this to be true. */
146
- export const cameraReady = ref(false)
147
-
148
- export const ensuredCamera = computed<Lunch.Node<THREE.Camera> | null>({
149
- get() {
150
- return (
151
- cameraReady.value ? (defaultCamera.value as any) : (null as any)
152
- ) as any
153
- },
154
- set(val: any) {
155
- const t = val.type ?? ''
156
- const pascalType = t[0].toUpperCase() + t.slice(1)
157
- overrides[pascalType] = val as any
158
- },
159
- })
160
-
161
- // ENSURE RENDERER
162
- // ====================
163
- export const fallbackRendererUuid = 'FALLBACK_RENDERER'
164
- export const ensuredRenderer = buildEnsured(
165
- // TODO: ensure support for css/svg renderers
166
- ['WebGLRenderer'], //, 'CSS2DRenderer', 'CSS3DRenderer', 'SVGRenderer'],
167
- fallbackRendererUuid,
168
- {}
169
- ) as unknown as WritableComputedRef<Lunch.Node<THREE.WebGLRenderer>>
170
- /** Special value to be changed ONLY in `LunchboxWrapper`.
171
- * Functions waiting for a Renderer need to wait for this to be true. */
172
- export const rendererReady = ref(false)
173
-
174
- export const ensureRenderer = computed<Lunch.Node<THREE.WebGLRenderer> | null>({
175
- get() {
176
- return (
177
- rendererReady.value ? (ensuredRenderer.value as any) : (null as any)
178
- ) as any
179
- },
180
- set(val: any) {
181
- const t = val.type ?? ''
182
- const pascalType = t[0].toUpperCase() + t.slice(1)
183
- overrides[pascalType] = val as any
184
- },
185
- })
186
-
187
- // ENSURE SCENE
188
- // ====================
189
- export const fallbackSceneUuid = 'FALLBACK_SCENE'
190
- export const ensuredScene = buildEnsured<THREE.Scene>(
191
- 'Scene',
192
- fallbackSceneUuid
193
- )
194
-
195
- // ENSURE AUTO-RAYCASTER
196
- export const autoRaycasterUuid = 'AUTO_RAYCASTER'
197
- // `unknown` is intentional here - we need to typecast the node since Raycaster isn't an Object3D
198
- export const ensuredRaycaster = buildEnsured(
199
- 'Raycaster',
200
- autoRaycasterUuid,
201
- {},
202
- (node) => setupAutoRaycaster(node as unknown as Lunch.Node<THREE.Raycaster>)
203
- ) as unknown as WritableComputedRef<Lunch.Node<THREE.Raycaster>>
@@ -1,102 +0,0 @@
1
- import { watch } from 'vue'
2
- import {
3
- addInteractable,
4
- interactables,
5
- removeInteractable,
6
- } from './interactables'
7
- import { ensuredRaycaster } from '..'
8
- import { inputActive } from './input'
9
- import { currentIntersections } from '.'
10
- import { Lunch } from '../..'
11
-
12
- export * from './input'
13
- export * from './interactables'
14
- export * from './setupAutoRaycaster'
15
-
16
- /** Add an event listener to the given node. Also creates the event teardown function and any necessary raycaster/interaction dictionary updates. */
17
- export function addEventListener({
18
- node,
19
- key,
20
- value,
21
- }: {
22
- node: Lunch.Node
23
- key: Lunch.EventKey
24
- value: Lunch.EventCallback
25
- }) {
26
- // create new records for this key if needed
27
- if (!node.eventListeners[key]) {
28
- node.eventListeners[key] = []
29
- }
30
- if (!node.eventListenerRemoveFunctions[key]) {
31
- node.eventListenerRemoveFunctions[key] = []
32
- }
33
-
34
- // add event listener
35
- node.eventListeners[key].push(value)
36
-
37
- // if we need it, let's get/create the main raycaster
38
- if (interactionsRequiringRaycaster.includes(key)) {
39
- // we're not using `v` here, we're just making sure the raycaster has been created
40
- // TODO: is this necessary?
41
- const v = ensuredRaycaster.value
42
-
43
- if (node.instance && !interactables.includes(node)) {
44
- addInteractable(node)
45
- node.eventListenerRemoveFunctions[key].push(() =>
46
- removeInteractable(node)
47
- )
48
- }
49
- }
50
-
51
- // register click, pointerdown, pointerup
52
- if (key === 'onClick' || key === 'onPointerDown' || key === 'onPointerUp') {
53
- const stop = watch(
54
- () => inputActive.value,
55
- (isDown) => {
56
- const idx = currentIntersections
57
- .map((v) => v.element)
58
- .findIndex(
59
- (v) =>
60
- v.instance &&
61
- v.instance.uuid === node.instance?.uuid
62
- )
63
- if (idx !== -1) {
64
- if (
65
- isDown &&
66
- (key === 'onClick' || key === 'onPointerDown')
67
- ) {
68
- node.eventListeners[key].forEach((func) => {
69
- func({
70
- intersection:
71
- currentIntersections[idx].intersection,
72
- })
73
- })
74
- } else if (!isDown && key === 'onPointerUp') {
75
- node.eventListeners[key].forEach((func) => {
76
- func({
77
- intersection:
78
- currentIntersections[idx].intersection,
79
- })
80
- })
81
- }
82
- }
83
- }
84
- )
85
-
86
- node.eventListenerRemoveFunctions[key].push(stop)
87
- }
88
-
89
- return node
90
- }
91
-
92
- const interactionsRequiringRaycaster = [
93
- 'onClick',
94
- 'onPointerUp',
95
- 'onPointerDown',
96
- 'onPointerOver',
97
- 'onPointerOut',
98
- 'onPointerEnter',
99
- 'onPointerLeave',
100
- 'onPointerMove',
101
- // 'onPointerMissed',
102
- ]
@@ -1,4 +0,0 @@
1
- import { ref } from 'vue'
2
-
3
- /** Mouse is down, touch is pressed, etc */
4
- export const inputActive = ref(false)
@@ -1,14 +0,0 @@
1
- import { Lunch } from '../..'
2
-
3
- export const interactables: Array<Lunch.Node> = []
4
-
5
- export const addInteractable = (target: Lunch.Node) => {
6
- interactables.push(target)
7
- }
8
-
9
- export const removeInteractable = (target: Lunch.Node) => {
10
- const idx = interactables.indexOf(target)
11
- if (idx !== -1) {
12
- interactables.splice(idx, 1)
13
- }
14
- }
@@ -1,224 +0,0 @@
1
- import { interactables } from '.'
2
- import {
3
- ensuredCamera,
4
- ensuredRaycaster,
5
- ensureRenderer,
6
- onBeforeRender,
7
- } from '..'
8
- import { globals, Lunch } from '../../'
9
- import { ref, watch, WatchStopHandle } from 'vue'
10
- import { inputActive } from './input'
11
- import { Intersection } from 'three'
12
-
13
- let mouseMoveListener: (event: MouseEvent) => void
14
- let mouseDownListener: (event: MouseEvent) => void
15
- let mouseUpListener: (event: MouseEvent) => void
16
-
17
- export const mousePos = ref({ x: Infinity, y: Infinity })
18
- let autoRaycasterEventsInitialized = false
19
-
20
- let frameID: number
21
-
22
- export const setupAutoRaycaster = (node: Lunch.Node<THREE.Raycaster>) => {
23
- const instance = node.instance
24
-
25
- if (!instance) return
26
-
27
- // add mouse events once renderer is ready
28
- let stopWatcher: WatchStopHandle | null = null
29
- stopWatcher = watch(
30
- () => ensureRenderer.value,
31
- (renderer) => {
32
- // make sure renderer exists
33
- if (!renderer?.instance) return
34
-
35
- // cancel early if autoraycaster exists
36
- if (autoRaycasterEventsInitialized) {
37
- if (stopWatcher) stopWatcher()
38
- return
39
- }
40
-
41
- // create mouse events
42
- mouseMoveListener = (evt) => {
43
- const screenWidth =
44
- (renderer.instance!.domElement.width ?? 1) /
45
- globals.dpr.value
46
- const screenHeight =
47
- (renderer.instance!.domElement.height ?? 1) /
48
- globals.dpr.value
49
-
50
- mousePos.value.x = (evt.offsetX / screenWidth) * 2 - 1
51
- mousePos.value.y = -(evt.offsetY / screenHeight) * 2 + 1
52
- }
53
- mouseDownListener = () => (inputActive.value = true)
54
- mouseUpListener = () => (inputActive.value = false)
55
-
56
- // add mouse events
57
- renderer.instance.domElement.addEventListener(
58
- 'mousemove',
59
- mouseMoveListener
60
- )
61
- renderer.instance.domElement.addEventListener(
62
- 'mousedown',
63
- mouseDownListener
64
- )
65
- renderer.instance.domElement.addEventListener(
66
- 'mouseup',
67
- mouseUpListener
68
- )
69
-
70
- // TODO: add touch events
71
-
72
- // process mouse events asynchronously, whenever the mouse state changes
73
- watch(
74
- () => [inputActive.value, mousePos.value.x, mousePos.value.y],
75
- () => {
76
- if (frameID) cancelAnimationFrame(frameID)
77
- frameID = requestAnimationFrame(() => {
78
- autoRaycasterBeforeRender()
79
- })
80
- }
81
- )
82
-
83
- // mark complete
84
- autoRaycasterEventsInitialized = true
85
-
86
- // cancel setup watcher
87
- if (stopWatcher) {
88
- stopWatcher()
89
- }
90
- },
91
- { immediate: true }
92
- )
93
- }
94
-
95
- // AUTO-RAYCASTER CALLBACK
96
- // ====================
97
- export let currentIntersections: Array<{
98
- element: Lunch.Node
99
- intersection: Intersection<THREE.Object3D>
100
- }> = []
101
-
102
- const autoRaycasterBeforeRender = () => {
103
- // setup
104
- const raycaster = ensuredRaycaster.value?.instance
105
- const camera = ensuredCamera.value?.instance
106
- if (!raycaster || !camera) return
107
-
108
- raycaster.setFromCamera(globals.mousePos.value, camera)
109
- const intersections = raycaster.intersectObjects(
110
- interactables.map((v) => v.instance as any as THREE.Object3D)
111
- )
112
-
113
- let enterValues: Array<Intersection<THREE.Object3D>> = [],
114
- sameValues: Array<Intersection<THREE.Object3D>> = [],
115
- leaveValues: Array<Intersection<THREE.Object3D>> = [],
116
- entering: Array<{
117
- element: Lunch.Node
118
- intersection: Intersection<THREE.Object3D>
119
- }> = [],
120
- staying: Array<{
121
- element: Lunch.Node
122
- intersection: Intersection<THREE.Object3D>
123
- }> = []
124
-
125
- // intersection arrays
126
- leaveValues = currentIntersections.map((v) => v.intersection)
127
-
128
- // element arrays
129
- intersections?.forEach((intersection) => {
130
- const currentIdx = currentIntersections.findIndex(
131
- (v) => v.intersection.object === intersection.object
132
- )
133
- if (currentIdx === -1) {
134
- // new intersection
135
- enterValues.push(intersection)
136
-
137
- const found = interactables.find(
138
- (v) => v.instance?.uuid === intersection.object.uuid
139
- )
140
- if (found) {
141
- entering.push({ element: found, intersection })
142
- }
143
- } else {
144
- // existing intersection
145
- sameValues.push(intersection)
146
-
147
- const found = interactables.find(
148
- (v) => v.instance?.uuid === intersection.object.uuid
149
- )
150
- if (found) {
151
- staying.push({ element: found, intersection })
152
- }
153
- }
154
- // this is a current intersection, so it won't be in our `leave` array
155
- const leaveIdx = leaveValues.findIndex(
156
- (v) => v.object.uuid === intersection.object.uuid
157
- )
158
- if (leaveIdx !== -1) {
159
- leaveValues.splice(leaveIdx, 1)
160
- }
161
- })
162
-
163
- const leaving: Array<{
164
- element: Lunch.Node
165
- intersection: Intersection<THREE.Object3D>
166
- }> = leaveValues.map((intersection) => {
167
- return {
168
- element: interactables.find(
169
- (interactable) =>
170
- interactable.instance?.uuid === intersection.object.uuid
171
- ) as any as Lunch.Node,
172
- intersection,
173
- }
174
- })
175
-
176
- // new interactions
177
- entering.forEach(({ element, intersection }) => {
178
- fireEventsFromIntersections({
179
- element,
180
- eventKeys: ['onPointerEnter'],
181
- intersection,
182
- })
183
- })
184
-
185
- // unchanged interactions
186
- staying.forEach(({ element, intersection }) => {
187
- const eventKeys: Array<Lunch.EventKey> = [
188
- 'onPointerOver',
189
- 'onPointerMove',
190
- ]
191
- fireEventsFromIntersections({ element, eventKeys, intersection })
192
- })
193
-
194
- // exited interactions
195
- leaving.forEach(({ element, intersection }) => {
196
- const eventKeys: Array<Lunch.EventKey> = [
197
- 'onPointerLeave',
198
- 'onPointerOut',
199
- ]
200
- fireEventsFromIntersections({ element, eventKeys, intersection })
201
- })
202
-
203
- currentIntersections = ([] as any).concat(entering, staying)
204
- }
205
-
206
- // utility function for firing multiple callbacks and multiple events on a Lunchbox.Element
207
- const fireEventsFromIntersections = ({
208
- element,
209
- eventKeys,
210
- intersection,
211
- }: {
212
- element: Lunch.Node
213
- eventKeys: Array<Lunch.EventKey>
214
- intersection: Intersection<THREE.Object3D>
215
- }) => {
216
- if (!element) return
217
- eventKeys.forEach((eventKey) => {
218
- if (element.eventListeners[eventKey]) {
219
- element.eventListeners[eventKey].forEach((cb) => {
220
- cb({ intersection })
221
- })
222
- }
223
- })
224
- }
package/src/core/start.ts DELETED
@@ -1,11 +0,0 @@
1
- import { Lunch } from '..'
2
-
3
- export const startCallbacks = [] as Lunch.UpdateCallback[]
4
-
5
- export const onStart = (cb: Lunch.UpdateCallback, index = Infinity) => {
6
- if (index === Infinity) {
7
- startCallbacks.push(cb)
8
- } else {
9
- startCallbacks.splice(index, 0, cb)
10
- }
11
- }