@dreamerbird/camera-studio 0.2.0 → 0.3.0
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/dist/CameraScene.d.ts +1 -1
- package/dist/CameraScene.d.ts.map +1 -1
- package/dist/CameraScene.js +36 -6
- package/dist/PlacementManager.d.ts +15 -1
- package/dist/PlacementManager.d.ts.map +1 -1
- package/dist/PlacementManager.js +123 -14
- package/dist/agent/api.d.ts +5 -0
- package/dist/agent/api.d.ts.map +1 -1
- package/dist/agent/api.js +25 -0
- package/dist/agent/api.test.js +75 -1
- package/dist/agent/commands/assets.d.ts +5 -0
- package/dist/agent/commands/assets.d.ts.map +1 -1
- package/dist/agent/commands/assets.js +93 -21
- package/dist/agent/commands/assets.test.js +53 -0
- package/dist/agent/commands/materials.d.ts +18 -0
- package/dist/agent/commands/materials.d.ts.map +1 -0
- package/dist/agent/commands/materials.js +192 -0
- package/dist/agent/commands/materials.test.d.ts +2 -0
- package/dist/agent/commands/materials.test.d.ts.map +1 -0
- package/dist/agent/commands/materials.test.js +294 -0
- package/dist/agent/commands/modelRecovery.test.d.ts +2 -0
- package/dist/agent/commands/modelRecovery.test.d.ts.map +1 -0
- package/dist/agent/commands/modelRecovery.test.js +122 -0
- package/dist/agent/commands/models.d.ts +13 -0
- package/dist/agent/commands/models.d.ts.map +1 -1
- package/dist/agent/commands/models.js +98 -1
- package/dist/agent/commands/primitives.d.ts +27 -0
- package/dist/agent/commands/primitives.d.ts.map +1 -0
- package/dist/agent/commands/primitives.js +117 -0
- package/dist/agent/commands/primitives.test.d.ts +2 -0
- package/dist/agent/commands/primitives.test.d.ts.map +1 -0
- package/dist/agent/commands/primitives.test.js +135 -0
- package/dist/agent/expectedCommands.d.ts +1 -1
- package/dist/agent/expectedCommands.d.ts.map +1 -1
- package/dist/agent/expectedCommands.js +6 -0
- package/dist/agent/propAccess.d.ts +2 -0
- package/dist/agent/propAccess.d.ts.map +1 -1
- package/dist/agent/propAccess.js +21 -0
- package/dist/agent/registerCommands.d.ts.map +1 -1
- package/dist/agent/registerCommands.js +5 -1
- package/dist/agent/runtime/objectRuntime.d.ts +2 -0
- package/dist/agent/runtime/objectRuntime.d.ts.map +1 -1
- package/dist/agent/runtime/objectRuntime.js +15 -1
- package/dist/agent/runtime.d.ts +1 -0
- package/dist/agent/runtime.d.ts.map +1 -1
- package/dist/agent/runtime.js +2 -0
- package/dist/assetLibrary/dispose.d.ts +4 -2
- package/dist/assetLibrary/dispose.d.ts.map +1 -1
- package/dist/assetLibrary/dispose.js +7 -5
- package/dist/assetLibrary/gltfLoader.d.ts +2 -1
- package/dist/assetLibrary/gltfLoader.d.ts.map +1 -1
- package/dist/assetLibrary/gltfLoader.js +2 -2
- package/dist/assetLibrary/gltfResourceSet.d.ts +6 -0
- package/dist/assetLibrary/gltfResourceSet.d.ts.map +1 -0
- package/dist/assetLibrary/gltfResourceSet.js +27 -0
- package/dist/assetLibrary/gltfResourceSet.test.d.ts +2 -0
- package/dist/assetLibrary/gltfResourceSet.test.d.ts.map +1 -0
- package/dist/assetLibrary/gltfResourceSet.test.js +40 -0
- package/dist/assetLibrary/importModelFile.d.ts +5 -0
- package/dist/assetLibrary/importModelFile.d.ts.map +1 -1
- package/dist/assetLibrary/importModelFile.js +3 -16
- package/dist/assetLibrary/importModelFiles.d.ts +15 -0
- package/dist/assetLibrary/importModelFiles.d.ts.map +1 -0
- package/dist/assetLibrary/importModelFiles.js +58 -0
- package/dist/assetLibrary/importModelFiles.test.d.ts +2 -0
- package/dist/assetLibrary/importModelFiles.test.d.ts.map +1 -0
- package/dist/assetLibrary/importModelFiles.test.js +217 -0
- package/dist/assetLibrary/importPipeline.d.ts +15 -1
- package/dist/assetLibrary/importPipeline.d.ts.map +1 -1
- package/dist/assetLibrary/importPipeline.js +74 -38
- package/dist/assetLibrary/importPipeline.test.js +25 -0
- package/dist/assetLibrary/library.d.ts +1 -0
- package/dist/assetLibrary/library.d.ts.map +1 -1
- package/dist/assetLibrary/library.js +22 -1
- package/dist/assetLibrary/library.test.js +76 -0
- package/dist/bootstrap.d.ts +2 -1
- package/dist/bootstrap.d.ts.map +1 -1
- package/dist/bootstrap.js +69 -7
- package/dist/bootstrap.test.js +111 -0
- package/dist/config.d.ts +10 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/hostFlows.d.ts +1 -0
- package/dist/hostFlows.d.ts.map +1 -1
- package/dist/hostFlows.js +9 -0
- package/dist/index.d.ts +10 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -1
- package/dist/materials/modelMaterials.d.ts +19 -0
- package/dist/materials/modelMaterials.d.ts.map +1 -0
- package/dist/materials/modelMaterials.js +253 -0
- package/dist/materials/modelMaterials.test.d.ts +2 -0
- package/dist/materials/modelMaterials.test.d.ts.map +1 -0
- package/dist/materials/modelMaterials.test.js +188 -0
- package/dist/materials/types.d.ts +20 -0
- package/dist/materials/types.d.ts.map +1 -0
- package/dist/materials/types.js +1 -0
- package/dist/materials/useModelMaterials.d.ts +10 -0
- package/dist/materials/useModelMaterials.d.ts.map +1 -0
- package/dist/materials/useModelMaterials.js +26 -0
- package/dist/materials/useModelMaterials.test.d.ts +2 -0
- package/dist/materials/useModelMaterials.test.d.ts.map +1 -0
- package/dist/materials/useModelMaterials.test.js +75 -0
- package/dist/poses.d.ts +38 -0
- package/dist/poses.d.ts.map +1 -0
- package/dist/poses.js +41 -0
- package/dist/poses.test.d.ts +2 -0
- package/dist/poses.test.d.ts.map +1 -0
- package/dist/poses.test.js +38 -0
- package/dist/primitives.d.ts +5 -0
- package/dist/primitives.d.ts.map +1 -0
- package/dist/primitives.js +15 -0
- package/dist/primitives.test.d.ts +2 -0
- package/dist/primitives.test.d.ts.map +1 -0
- package/dist/primitives.test.js +27 -0
- package/dist/reset.realStudio.test.d.ts +2 -0
- package/dist/reset.realStudio.test.d.ts.map +1 -0
- package/dist/reset.realStudio.test.js +60 -0
- package/dist/studioRoot.d.ts +4 -0
- package/dist/studioRoot.d.ts.map +1 -0
- package/dist/studioRoot.js +36 -0
- package/dist/studioRoot.test.d.ts +2 -0
- package/dist/studioRoot.test.d.ts.map +1 -0
- package/dist/studioRoot.test.js +62 -0
- package/dist/useCameraStudio.d.ts.map +1 -1
- package/dist/useCameraStudio.js +3 -2
- package/package.json +9 -1
package/dist/poses.js
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 内置静态姿势库的公开入口(包根与 `@dreamerbird/camera-studio/poses`
|
|
3
|
+
* 子路径同时导出)。
|
|
4
|
+
*
|
|
5
|
+
* 姿势数据随包编译进 JS,不依赖 IndexedDB / 网络 / React。引用本包的
|
|
6
|
+
* 项目可以直接列出姿势、把 `entry.bones` 写进自己的 THREE 骨架,或交给
|
|
7
|
+
* Agent API 的 `poses.applyCustom`。
|
|
8
|
+
*
|
|
9
|
+
* - id 稳定:`seed-pose-<key>`,与场景里已保存的姿势引用一致。
|
|
10
|
+
* - rig 是内置族代表模型(Y Bot)的 Mixamo 骨架 id;导入同款 Mixamo
|
|
11
|
+
* 模型得到的 rig 与之相等,跨项目可直接复用。
|
|
12
|
+
* - QBX 手势条目只包含手指骨,是叠加片段,不覆盖身体姿势。
|
|
13
|
+
*/
|
|
14
|
+
import { applyPose, captureBindPose, capturePose } from './assetLibrary/pose';
|
|
15
|
+
import { buildBuiltinPoseEntries, builtinPoseRig, } from './assetLibrary/seedBuiltinPoses';
|
|
16
|
+
export { applyPose, captureBindPose, capturePose };
|
|
17
|
+
/** 内置姿势所属骨架族(内置 Y Bot 的 rig id) */
|
|
18
|
+
export const BUILTIN_POSE_RIG = builtinPoseRig();
|
|
19
|
+
/** 内置姿势数据来源,用于署名/展示;不影响运行时行为 */
|
|
20
|
+
export const BUILTIN_POSE_SOURCES = [
|
|
21
|
+
{ id: 'procedural', name: '内置程序化基础姿势' },
|
|
22
|
+
{ id: 'dpl', name: 'Dynamic Pose Library Free (BlendAtlas)' },
|
|
23
|
+
{ id: 'qbx', name: 'QBX Pose Asset Pack' },
|
|
24
|
+
];
|
|
25
|
+
let entriesById;
|
|
26
|
+
/**
|
|
27
|
+
* 列出全部内置静态姿势。返回的条目是包内共享的只读数据,请勿原地修改;
|
|
28
|
+
* 需要改骨骼时先拷贝 `bones`。
|
|
29
|
+
*/
|
|
30
|
+
export function listBuiltinPoses() {
|
|
31
|
+
if (!entriesById) {
|
|
32
|
+
const entries = buildBuiltinPoseEntries(BUILTIN_POSE_RIG);
|
|
33
|
+
entriesById = new Map(entries.map((entry) => [entry.id, entry]));
|
|
34
|
+
}
|
|
35
|
+
return [...entriesById.values()];
|
|
36
|
+
}
|
|
37
|
+
/** 按稳定 id 取内置姿势;未知 id 返回 undefined。 */
|
|
38
|
+
export function getBuiltinPose(id) {
|
|
39
|
+
listBuiltinPoses();
|
|
40
|
+
return entriesById.get(id);
|
|
41
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"poses.test.d.ts","sourceRoot":"","sources":["../src/poses.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { Bone, Object3D } from 'three';
|
|
2
|
+
import { applyPose, BUILTIN_POSE_RIG, getBuiltinPose, listBuiltinPoses, } from './poses';
|
|
3
|
+
describe('内置静态姿势公开 API', () => {
|
|
4
|
+
test('条目 id 稳定、唯一,并且都属于内置骨架族', () => {
|
|
5
|
+
const poses = listBuiltinPoses();
|
|
6
|
+
expect(poses.length).toBeGreaterThan(100);
|
|
7
|
+
expect(new Set(poses.map((p) => p.id)).size).toBe(poses.length);
|
|
8
|
+
for (const pose of poses) {
|
|
9
|
+
expect(pose.kind).toBe('pose');
|
|
10
|
+
expect(pose.id.startsWith('seed-pose-')).toBe(true);
|
|
11
|
+
expect(pose.name.length).toBeGreaterThan(0);
|
|
12
|
+
expect(pose.rig).toBe(BUILTIN_POSE_RIG);
|
|
13
|
+
}
|
|
14
|
+
});
|
|
15
|
+
test('按稳定 id 取姿势;未知 id 返回 undefined', () => {
|
|
16
|
+
const pose = getBuiltinPose('seed-pose-stand-relaxed');
|
|
17
|
+
expect(pose?.name).toBe('站立放松');
|
|
18
|
+
expect(pose?.bones.mixamorigHips.rotation).toHaveLength(4);
|
|
19
|
+
expect(getBuiltinPose('seed-pose-does-not-exist')).toBeUndefined();
|
|
20
|
+
});
|
|
21
|
+
test('手势条目只含手指骨,不会覆盖身体', () => {
|
|
22
|
+
const gesture = getBuiltinPose('seed-pose-qbx-hands-02-other-fist-l');
|
|
23
|
+
expect(gesture).toBeDefined();
|
|
24
|
+
const finger = /^mixamorig(Left|Right)Hand(Thumb|Index|Middle|Ring|Pinky)\d$/;
|
|
25
|
+
expect(Object.keys(gesture.bones).every((name) => finger.test(name))).toBe(true);
|
|
26
|
+
});
|
|
27
|
+
test('applyPose 按骨骼名写入,缺失骨计入 missed', () => {
|
|
28
|
+
const root = new Object3D();
|
|
29
|
+
const hips = new Bone();
|
|
30
|
+
hips.name = 'mixamorigHips';
|
|
31
|
+
root.add(hips);
|
|
32
|
+
const pose = getBuiltinPose('seed-pose-stand-relaxed');
|
|
33
|
+
const result = applyPose(root, pose.bones);
|
|
34
|
+
expect(result.applied).toBe(1);
|
|
35
|
+
expect(result.missed).toHaveLength(Object.keys(pose.bones).length - 1);
|
|
36
|
+
expect(hips.quaternion.toArray()).toEqual(pose.bones.mixamorigHips.rotation);
|
|
37
|
+
});
|
|
38
|
+
});
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { Mesh } from 'three';
|
|
2
|
+
export type PrimitiveKind = 'plane' | 'box' | 'sphere' | 'cylinder';
|
|
3
|
+
export declare const PRIMITIVE_KINDS: readonly ["plane", "box", "sphere", "cylinder"];
|
|
4
|
+
export declare function createPrimitive(kind: PrimitiveKind): Mesh;
|
|
5
|
+
//# sourceMappingURL=primitives.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"primitives.d.ts","sourceRoot":"","sources":["../src/primitives.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,IAAI,EAIL,MAAM,OAAO,CAAA;AAEd,MAAM,MAAM,aAAa,GAAG,OAAO,GAAG,KAAK,GAAG,QAAQ,GAAG,UAAU,CAAA;AACnE,eAAO,MAAM,eAAe,iDAAkD,CAAA;AAE9E,wBAAgB,eAAe,CAAC,IAAI,EAAE,aAAa,GAAG,IAAI,CAYzD"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { BoxGeometry, CylinderGeometry, Mesh, MeshStandardMaterial, PlaneGeometry, SphereGeometry, } from 'three';
|
|
2
|
+
export const PRIMITIVE_KINDS = ['plane', 'box', 'sphere', 'cylinder'];
|
|
3
|
+
export function createPrimitive(kind) {
|
|
4
|
+
const geometry = {
|
|
5
|
+
plane: () => new PlaneGeometry(2, 2),
|
|
6
|
+
box: () => new BoxGeometry(1, 1, 1),
|
|
7
|
+
sphere: () => new SphereGeometry(0.5, 32, 16),
|
|
8
|
+
cylinder: () => new CylinderGeometry(0.5, 0.5, 1, 32),
|
|
9
|
+
}[kind]();
|
|
10
|
+
const mesh = new Mesh(geometry, new MeshStandardMaterial({ color: '#b8b8b8' }));
|
|
11
|
+
mesh.name = `Primitive/${kind}`;
|
|
12
|
+
mesh.castShadow = true;
|
|
13
|
+
mesh.receiveShadow = true;
|
|
14
|
+
return mesh;
|
|
15
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"primitives.test.d.ts","sourceRoot":"","sources":["../src/primitives.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { BoxGeometry, CylinderGeometry, PlaneGeometry, SphereGeometry, } from 'three';
|
|
2
|
+
import { createPrimitive } from './primitives';
|
|
3
|
+
test('creates the four metric primitive kinds', () => {
|
|
4
|
+
expect(createPrimitive('plane').geometry).toBeInstanceOf(PlaneGeometry);
|
|
5
|
+
expect(createPrimitive('box').geometry).toBeInstanceOf(BoxGeometry);
|
|
6
|
+
expect(createPrimitive('sphere').geometry).toBeInstanceOf(SphereGeometry);
|
|
7
|
+
expect(createPrimitive('cylinder').geometry).toBeInstanceOf(CylinderGeometry);
|
|
8
|
+
});
|
|
9
|
+
test('uses the specified metric dimensions and tessellation', () => {
|
|
10
|
+
const plane = createPrimitive('plane').geometry;
|
|
11
|
+
const box = createPrimitive('box').geometry;
|
|
12
|
+
const sphere = createPrimitive('sphere').geometry;
|
|
13
|
+
const cylinder = createPrimitive('cylinder').geometry;
|
|
14
|
+
expect(plane.parameters).toMatchObject({ width: 2, height: 2 });
|
|
15
|
+
expect(box.parameters).toMatchObject({ width: 1, height: 1, depth: 1 });
|
|
16
|
+
expect(sphere.parameters).toMatchObject({
|
|
17
|
+
radius: 0.5,
|
|
18
|
+
widthSegments: 32,
|
|
19
|
+
heightSegments: 16,
|
|
20
|
+
});
|
|
21
|
+
expect(cylinder.parameters).toMatchObject({
|
|
22
|
+
radiusTop: 0.5,
|
|
23
|
+
radiusBottom: 0.5,
|
|
24
|
+
height: 1,
|
|
25
|
+
radialSegments: 32,
|
|
26
|
+
});
|
|
27
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reset.realStudio.test.d.ts","sourceRoot":"","sources":["../src/reset.realStudio.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* @jest-environment node
|
|
3
|
+
*/
|
|
4
|
+
import { types as t, val } from '@dreamerbird/core';
|
|
5
|
+
import { globals } from '@dreamerbird/core/globals';
|
|
6
|
+
import { getCoreRafDriver } from '@dreamerbird/core/coreTicker';
|
|
7
|
+
import getStudio from '@dreamerbird/studio/getStudio';
|
|
8
|
+
jest.doMock('jose', () => ({ decodeJwt: jest.fn() }));
|
|
9
|
+
let bootstrap;
|
|
10
|
+
let renderSpy;
|
|
11
|
+
beforeAll(async () => {
|
|
12
|
+
bootstrap = await import('./bootstrap');
|
|
13
|
+
renderSpy = jest.spyOn(getStudio().ui, 'render').mockImplementation(() => { });
|
|
14
|
+
});
|
|
15
|
+
afterAll(() => {
|
|
16
|
+
renderSpy.mockRestore();
|
|
17
|
+
getCoreRafDriver().stop?.();
|
|
18
|
+
});
|
|
19
|
+
function sceneState(value) {
|
|
20
|
+
return {
|
|
21
|
+
definitionVersion: globals.currentProjectStateDefinitionVersion,
|
|
22
|
+
sheetsById: {
|
|
23
|
+
Scene: {
|
|
24
|
+
staticOverrides: { byObject: { Only: { value } } },
|
|
25
|
+
sequence: {
|
|
26
|
+
type: 'PositionalSequence',
|
|
27
|
+
length: 10,
|
|
28
|
+
subUnitsPerUnit: 30,
|
|
29
|
+
tracksByObject: {},
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
test('reset restores a same-named project from its new initialState with real core/studio', async () => {
|
|
36
|
+
const projectId = 'camera-studio-reset-real';
|
|
37
|
+
const first = await bootstrap.initCameraStudio({
|
|
38
|
+
project: projectId,
|
|
39
|
+
storage: false,
|
|
40
|
+
state: { initialState: sceneState(1), onStateChange: () => { } },
|
|
41
|
+
});
|
|
42
|
+
const firstProject = first.sceneProps.project;
|
|
43
|
+
await firstProject.ready;
|
|
44
|
+
expect(val(firstProject.sheet('Scene').object('Only', { value: t.number(0) }).props
|
|
45
|
+
.value)).toBe(1);
|
|
46
|
+
await bootstrap.disposeCameraStudio(first);
|
|
47
|
+
await bootstrap.resetCameraStudio();
|
|
48
|
+
const second = await bootstrap.initCameraStudio({
|
|
49
|
+
project: projectId,
|
|
50
|
+
storage: false,
|
|
51
|
+
state: { initialState: sceneState(2), onStateChange: () => { } },
|
|
52
|
+
});
|
|
53
|
+
const secondProject = second.sceneProps.project;
|
|
54
|
+
await secondProject.ready;
|
|
55
|
+
expect(secondProject).not.toBe(firstProject);
|
|
56
|
+
expect(val(secondProject.sheet('Scene').object('Only', { value: t.number(0) }).props
|
|
57
|
+
.value)).toBe(2);
|
|
58
|
+
await bootstrap.disposeCameraStudio(second);
|
|
59
|
+
await bootstrap.resetCameraStudio();
|
|
60
|
+
});
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export declare function resetStudioRootCache(): void;
|
|
2
|
+
export declare function mountStudioRoot(container?: HTMLElement): HTMLElement | undefined;
|
|
3
|
+
export declare function mountStudioRootWhenReady(container?: HTMLElement): Promise<HTMLElement | undefined>;
|
|
4
|
+
//# sourceMappingURL=studioRoot.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"studioRoot.d.ts","sourceRoot":"","sources":["../src/studioRoot.ts"],"names":[],"mappings":"AAUA,wBAAgB,oBAAoB,IAAI,IAAI,CAE3C;AAED,wBAAgB,eAAe,CAC7B,SAAS,CAAC,EAAE,WAAW,GACtB,WAAW,GAAG,SAAS,CAczB;AAED,wBAAsB,wBAAwB,CAC5C,SAAS,CAAC,EAAE,WAAW,GACtB,OAAO,CAAC,WAAW,GAAG,SAAS,CAAC,CAQlC"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
let retainedRoot;
|
|
2
|
+
/** 纯 Node/SSR 没有 DOM:挂载流程应直接跳过,而不是轮询等待。 */
|
|
3
|
+
function hasDom() {
|
|
4
|
+
return (typeof document !== 'undefined' &&
|
|
5
|
+
typeof document.getElementById === 'function');
|
|
6
|
+
}
|
|
7
|
+
export function resetStudioRootCache() {
|
|
8
|
+
retainedRoot = undefined;
|
|
9
|
+
}
|
|
10
|
+
export function mountStudioRoot(container) {
|
|
11
|
+
if (!hasDom())
|
|
12
|
+
return undefined;
|
|
13
|
+
const root = document.getElementById('theatrejs-studio-root') ?? retainedRoot;
|
|
14
|
+
if (!root)
|
|
15
|
+
return undefined;
|
|
16
|
+
retainedRoot = root;
|
|
17
|
+
const target = container ?? document.body;
|
|
18
|
+
target.appendChild(root);
|
|
19
|
+
const embedded = target !== document.body;
|
|
20
|
+
root.style.position = embedded ? 'absolute' : 'fixed';
|
|
21
|
+
root.style.inset = '0';
|
|
22
|
+
root.style.transform = embedded ? 'translateZ(0)' : '';
|
|
23
|
+
root.style.zIndex = embedded ? '40' : '100';
|
|
24
|
+
return root;
|
|
25
|
+
}
|
|
26
|
+
export async function mountStudioRootWhenReady(container) {
|
|
27
|
+
if (!hasDom())
|
|
28
|
+
return undefined;
|
|
29
|
+
for (let attempt = 0; attempt < 3000; attempt += 1) {
|
|
30
|
+
const root = mountStudioRoot(container);
|
|
31
|
+
if (root)
|
|
32
|
+
return root;
|
|
33
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
34
|
+
}
|
|
35
|
+
return undefined;
|
|
36
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"studioRoot.test.d.ts","sourceRoot":"","sources":["../src/studioRoot.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* @jest-environment jsdom
|
|
3
|
+
*/
|
|
4
|
+
import { mountStudioRoot, mountStudioRootWhenReady, resetStudioRootCache, } from './studioRoot';
|
|
5
|
+
function createStudioRoot() {
|
|
6
|
+
const root = document.createElement('div');
|
|
7
|
+
root.id = 'theatrejs-studio-root';
|
|
8
|
+
root.style.position = 'fixed';
|
|
9
|
+
root.style.inset = '0';
|
|
10
|
+
document.body.appendChild(root);
|
|
11
|
+
return root;
|
|
12
|
+
}
|
|
13
|
+
afterEach(() => {
|
|
14
|
+
resetStudioRootCache();
|
|
15
|
+
document.body.innerHTML = '';
|
|
16
|
+
});
|
|
17
|
+
test('mountStudioRoot scopes fixed Studio panels to the host editor', () => {
|
|
18
|
+
const root = createStudioRoot();
|
|
19
|
+
const editor = document.createElement('main');
|
|
20
|
+
document.body.appendChild(editor);
|
|
21
|
+
expect(mountStudioRoot(editor)).toBe(root);
|
|
22
|
+
expect(root.parentElement).toBe(editor);
|
|
23
|
+
expect(root.style.position).toBe('absolute');
|
|
24
|
+
expect(root.style.inset).toBe('0');
|
|
25
|
+
expect(root.style.transform).toBe('translateZ(0)');
|
|
26
|
+
expect(root.style.zIndex).toBe('40');
|
|
27
|
+
});
|
|
28
|
+
test('mountStudioRoot restores the default viewport mounting', () => {
|
|
29
|
+
const root = createStudioRoot();
|
|
30
|
+
const editor = document.createElement('main');
|
|
31
|
+
document.body.appendChild(editor);
|
|
32
|
+
mountStudioRoot(editor);
|
|
33
|
+
mountStudioRoot();
|
|
34
|
+
expect(root.parentElement).toBe(document.body);
|
|
35
|
+
expect(root.style.position).toBe('fixed');
|
|
36
|
+
expect(root.style.transform).toBe('');
|
|
37
|
+
expect(root.style.zIndex).toBe('100');
|
|
38
|
+
});
|
|
39
|
+
test('mountStudioRoot is a no-op before Studio creates its root', () => {
|
|
40
|
+
expect(mountStudioRoot(document.createElement('main'))).toBeUndefined();
|
|
41
|
+
});
|
|
42
|
+
test('mountStudioRootWhenReady waits for the asynchronously appended root', async () => {
|
|
43
|
+
const editor = document.createElement('main');
|
|
44
|
+
document.body.appendChild(editor);
|
|
45
|
+
const mounted = mountStudioRootWhenReady(editor);
|
|
46
|
+
setTimeout(createStudioRoot, 10);
|
|
47
|
+
const root = await mounted;
|
|
48
|
+
expect(root?.parentElement).toBe(editor);
|
|
49
|
+
expect(root?.style.position).toBe('absolute');
|
|
50
|
+
});
|
|
51
|
+
test('mountStudioRoot remounts a root retained from an unmounted editor', () => {
|
|
52
|
+
const root = createStudioRoot();
|
|
53
|
+
const firstEditor = document.createElement('main');
|
|
54
|
+
document.body.appendChild(firstEditor);
|
|
55
|
+
mountStudioRoot(firstEditor);
|
|
56
|
+
firstEditor.remove();
|
|
57
|
+
const secondEditor = document.createElement('main');
|
|
58
|
+
document.body.appendChild(secondEditor);
|
|
59
|
+
expect(mountStudioRoot(secondEditor)).toBe(root);
|
|
60
|
+
expect(root.parentElement).toBe(secondEditor);
|
|
61
|
+
expect(root.isConnected).toBe(true);
|
|
62
|
+
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useCameraStudio.d.ts","sourceRoot":"","sources":["../src/useCameraStudio.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAC,kBAAkB,EAAE,kBAAkB,EAAC,MAAM,UAAU,CAAA;AAmBpE,wBAAgB,eAAe,CAC7B,MAAM,GAAE,kBAAuB,GAC9B,kBAAkB,
|
|
1
|
+
{"version":3,"file":"useCameraStudio.d.ts","sourceRoot":"","sources":["../src/useCameraStudio.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAC,kBAAkB,EAAE,kBAAkB,EAAC,MAAM,UAAU,CAAA;AAmBpE,wBAAgB,eAAe,CAC7B,MAAM,GAAE,kBAAuB,GAC9B,kBAAkB,CA0EpB"}
|
package/dist/useCameraStudio.js
CHANGED
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
*
|
|
4
4
|
* - config 只在首帧取一次(useRef 锁存):后续渲染传新配置不热更,
|
|
5
5
|
* 与 theatre.init 的一次性语义对齐;已 boot 后重挂换 config 不生效
|
|
6
|
-
* (模块级 promise 复用首 boot),console.warn
|
|
7
|
-
*
|
|
6
|
+
* (模块级 promise 复用首 boot),console.warn 点名差异键;换配置需
|
|
7
|
+
* 整页刷新,或在全部消费者卸载后显式 resetCameraStudio。
|
|
8
8
|
* - 状态机:idle → initializing → ready / error;init 挂 bootstrap 的
|
|
9
9
|
* 共享 promise,StrictMode 双跑/重复触发只复用。
|
|
10
10
|
* - 单页单实例(studio 全局单例):同页第二个实例不占位、直接 error。
|
|
@@ -82,5 +82,6 @@ export function useCameraStudio(config = {}) {
|
|
|
82
82
|
error: phase.error,
|
|
83
83
|
sceneProps,
|
|
84
84
|
api: getCameraStudioApi(),
|
|
85
|
+
flushState: () => phase.ctx?.flushState() ?? Promise.resolve(),
|
|
85
86
|
};
|
|
86
87
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dreamerbird/camera-studio",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -12,6 +12,14 @@
|
|
|
12
12
|
"./contract-tests": {
|
|
13
13
|
"types": "./dist/assetLibrary/contractTests.d.ts",
|
|
14
14
|
"default": "./dist/assetLibrary/contractTests.js"
|
|
15
|
+
},
|
|
16
|
+
"./library-source": {
|
|
17
|
+
"types": "./dist/assetLibrary/source.d.ts",
|
|
18
|
+
"default": "./dist/assetLibrary/source.js"
|
|
19
|
+
},
|
|
20
|
+
"./poses": {
|
|
21
|
+
"types": "./dist/poses.d.ts",
|
|
22
|
+
"default": "./dist/poses.js"
|
|
15
23
|
}
|
|
16
24
|
},
|
|
17
25
|
"files": [
|