Skip to content

Commit 2beab7e

Browse files
Merge pull request #68 from TriVR-TRPG/codex/update-tag-assignment-in-createimportgroup
Defer import-root tagging and enforce undefined-usage policy for slot updates
2 parents 9b5c24f + 61a0769 commit 2beab7e

11 files changed

Lines changed: 99 additions & 50 deletions

File tree

.github/workflows/release-build.yml

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -62,11 +62,7 @@ jobs:
6262
6363
$patterns = @(
6464
"dist/*.zip",
65-
"dist/*.exe",
66-
"dist/*-macos",
67-
"dist/*-linux",
6865
"release/*.zip",
69-
"release/*.exe",
7066
"release/*.dmg",
7167
"release/*.AppImage",
7268
"release/latest*.yml"
@@ -106,9 +102,6 @@ jobs:
106102
make_latest: false
107103
files: |
108104
release-artifacts/*.zip
109-
release-artifacts/*.exe
110-
release-artifacts/*-macos
111-
release-artifacts/*-linux
112105
release-artifacts/*.AppImage
113106
release-artifacts/latest*.yml
114107
fail_on_unmatched_files: false

src/gui/main.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -217,7 +217,7 @@ async function handleImportToResonite(options: ImportOptions): Promise<ImportRes
217217
// rootScale from the UI is only used as the default for fresh imports.
218218
const groupName = `Udonarium Import - ${path.basename(filePath, '.zip')}`;
219219
const defaultScale = { x: rootScale, y: rootScale, z: rootScale };
220-
await slotBuilder.createImportGroup(
220+
const groupId = await slotBuilder.createImportGroup(
221221
groupName,
222222
previousImport.transform,
223223
defaultScale,
@@ -284,6 +284,8 @@ async function handleImportToResonite(options: ImportOptions): Promise<ImportRes
284284
{ enableSimpleAvatarProtection }
285285
);
286286

287+
await slotBuilder.tagImportGroupRoot(groupId);
288+
287289
client.disconnect();
288290
sendProgress('import', 100, '完了');
289291

src/i18n/locales/en.json

Lines changed: 21 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -42,33 +42,42 @@
4242
},
4343
"gui": {
4444
"subtitle": "Import Udonarium save data into Resonite",
45-
"selectFile": "Select ZIP File",
4645
"selectFilePlaceholder": "Select a file...",
4746
"browse": "Browse...",
48-
"analysisResult": "Analysis Result",
49-
"xmlFiles": "XML Files",
50-
"imageFiles": "Image Files",
51-
"objects": "Objects",
52-
"settings": "ResoniteLink Settings",
53-
"host": "Host:",
54-
"port": "Port:",
47+
"host": "ResoniteLink Host",
48+
"resoniteLinkPort": "ResoniteLink Port",
49+
"portHelpAriaLabel": "ResoniteLink port help",
50+
"portHelpTooltip": "Click to view how to configure ResoniteLink",
51+
"portHelpTitle": "How to configure ResoniteLink",
52+
"portHelpStep1": "Open a world where you are the host",
53+
"portHelpStep2": "In the Session tab, enable \"ResoniteLink\"",
54+
"portHelpStep3": "Enter the ResoniteLink port number",
55+
"dropZipOnly": "Please drop a ZIP file",
56+
"rootGrabbable": "Add Grabbable to root",
57+
"simpleAvatarProtection": "Add SimpleAvatarProtection",
58+
"rootScale": "Root Scale",
59+
"lockedTerrainCharacterCollider": "Enable collider on table and locked terrain",
60+
"semiTransparentImageBlendMode": "Blend mode for semi-transparent images",
61+
"blendModeCutout": "Cutout",
62+
"blendModeAlpha": "Alpha",
5563
"settingsHint": "Start Resonite and enable ResoniteLink.",
56-
"import": "Import",
64+
"mainControlsTitle": "Import Settings",
65+
"advancedOptions": "Advanced Options",
66+
"advancedOptionsTitle": "Advanced Options",
67+
"importLogTitle": "Import Log",
5768
"importToResonite": "Import to Resonite",
5869
"preparing": "Preparing...",
5970
"importComplete": "Import Complete!",
6071
"images": "Images: {imported}/{total}",
6172
"objectsResult": "Objects: {imported}/{total}",
6273
"checkResonite": "Check Resonite to see the imported objects.",
63-
"error": "Error",
6474
"errorOccurred": "An error occurred",
6575
"ensureResonite": "Make sure Resonite is running.",
76+
"ensureSupportedObjects": "Use a ZIP file that contains supported Udonarium objects.",
6677
"extracting": "Extracting ZIP file...",
6778
"parsingObjects": "Parsing objects...",
6879
"connectingResonite": "Connecting to ResoniteLink...",
6980
"importingData": "Importing...",
70-
"importingImages": "Importing images... {current}/{total}",
71-
"creatingObjects": "Creating objects... {current}/{total}",
7281
"complete": "Complete"
7382
},
7483
"objectTypes": {

src/i18n/locales/ja.json

Lines changed: 21 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -42,33 +42,42 @@
4242
},
4343
"gui": {
4444
"subtitle": "UdonariumのセーブデータをResoniteにインポート",
45-
"selectFile": "ZIPファイルを選択",
4645
"selectFilePlaceholder": "ファイルを選択してください...",
4746
"browse": "選択",
48-
"analysisResult": "解析結果",
49-
"xmlFiles": "XMLファイル",
50-
"imageFiles": "画像ファイル",
51-
"objects": "オブジェクト",
52-
"settings": "ResoniteLink設定",
53-
"host": "ホスト:",
54-
"port": "ポート:",
47+
"host": "ResoniteLinkホスト",
48+
"resoniteLinkPort": "ResoniteLinkポート",
49+
"portHelpAriaLabel": "ResoniteLinkポートのヘルプ",
50+
"portHelpTooltip": "ResoniteLinkの設定方法を表示",
51+
"portHelpTitle": "ResoniteLinkの設定方法",
52+
"portHelpStep1": "自分がホストのワールドを開く",
53+
"portHelpStep2": "セッションタブで「ResoniteLinkを有効化」",
54+
"portHelpStep3": "ResoniteLinkのポート番号を入力",
55+
"dropZipOnly": "ZIPファイルをドロップしてください",
56+
"rootGrabbable": "ルートにGrabbableを付ける",
57+
"simpleAvatarProtection": "SimpleAvatarProtectionを付ける",
58+
"rootScale": "ルートスケール",
59+
"lockedTerrainCharacterCollider": "テーブルと固定した地形にコライダーを付ける",
60+
"semiTransparentImageBlendMode": "半透明画像の描画方式",
61+
"blendModeCutout": "Cutout",
62+
"blendModeAlpha": "Alpha",
5563
"settingsHint": "Resoniteを起動し、ResoniteLinkを有効にしてください。",
56-
"import": "インポート",
64+
"mainControlsTitle": "インポート設定",
65+
"advancedOptions": "高度なオプション",
66+
"advancedOptionsTitle": "高度なオプション",
67+
"importLogTitle": "インポート実行ログ",
5768
"importToResonite": "Resoniteにインポート",
5869
"preparing": "準備中...",
5970
"importComplete": "インポート完了!",
6071
"images": "画像: {imported}/{total}",
6172
"objectsResult": "オブジェクト: {imported}/{total}",
6273
"checkResonite": "Resoniteで確認してください",
63-
"error": "エラー",
6474
"errorOccurred": "エラーが発生しました",
6575
"ensureResonite": "Resoniteが起動しているか確認してください。",
76+
"ensureSupportedObjects": "対応オブジェクトを含むUdonariumのZIPファイルを指定してください。",
6677
"extracting": "ZIPファイルを解凍中...",
6778
"parsingObjects": "オブジェクトを解析中...",
6879
"connectingResonite": "ResoniteLinkに接続中...",
6980
"importingData": "インポート中...",
70-
"importingImages": "画像をインポート中... {current}/{total}",
71-
"creatingObjects": "オブジェクトを作成中... {current}/{total}",
7281
"complete": "完了"
7382
},
7483
"objectTypes": {

src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -426,6 +426,8 @@ async function run(options: CLIOptions): Promise<void> {
426426
}
427427
}
428428
429+
await slotBuilder.tagImportGroupRoot(groupId);
430+
429431
const successImages = importedImages - failedImages.length;
430432
const successObjects = builtSlots - failedSlots.length;
431433
importSpinner.succeed(

src/parser/objects/TerrainParser.test.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ describe('TerrainParser', () => {
5757
expect(result.name).toBe('fallback-terrain.xml');
5858
});
5959

60-
it('should default dimensions to 1', () => {
60+
it('should default dimensions to 0', () => {
6161
const data = {
6262
data: [
6363
{
@@ -74,9 +74,9 @@ describe('TerrainParser', () => {
7474

7575
const result = parseTerrain(data, 'test.xml');
7676

77-
expect(result.width).toBe(1);
78-
expect(result.height).toBe(1);
79-
expect(result.depth).toBe(1);
77+
expect(result.width).toBe(0);
78+
expect(result.height).toBe(0);
79+
expect(result.depth).toBe(0);
8080
});
8181

8282
it('should preserve depth=0 when explicitly provided', () => {
@@ -348,9 +348,9 @@ describe('TerrainParser', () => {
348348

349349
expect(result.id).toBe('empty.xml');
350350
expect(result.name).toBe('empty.xml');
351-
expect(result.width).toBe(1);
352-
expect(result.height).toBe(1);
353-
expect(result.depth).toBe(1);
351+
expect(result.width).toBe(0);
352+
expect(result.height).toBe(0);
353+
expect(result.depth).toBe(0);
354354
expect(result.isLocked).toBe(false);
355355
expect(result.mode).toBe(0);
356356
expect(result.rotate).toBe(0);

src/parser/objects/TerrainParser.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,9 @@ export function parseTerrain(data: unknown, fileName: string): Terrain {
3434
// Parse common data
3535
const commonData = findDataByName(terrainData, 'common');
3636
const name = getTextValue(findDataByName(commonData, 'name')) || fileName;
37-
const width = getNumberValue(findDataByName(commonData, 'width')) ?? 1;
38-
const height = getNumberValue(findDataByName(commonData, 'height')) ?? 1;
39-
const depth = getNumberValue(findDataByName(commonData, 'depth')) ?? 1;
37+
const width = getNumberValue(findDataByName(commonData, 'width')) ?? 0;
38+
const height = getNumberValue(findDataByName(commonData, 'height')) ?? 0;
39+
const depth = getNumberValue(findDataByName(commonData, 'depth')) ?? 0;
4040

4141
// Parse position
4242
const position = parsePosition(root);

src/resonite/ResoniteLinkClient.test.ts

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,6 @@ describe('ResoniteLinkClient', () => {
7373
parentId: 'Root',
7474
name: 'Test',
7575
position: { x: 0, y: 0, z: 0 },
76-
tag: 'tagged',
7776
});
7877

7978
expect(id).toBe('slot-id');
@@ -83,7 +82,7 @@ describe('ResoniteLinkClient', () => {
8382
const data = payload.data as Record<string, unknown>;
8483
expect(data.id).toBe('slot-id');
8584
expect(data.parent).toEqual(expect.objectContaining({ targetId: 'Root' }));
86-
expect(data.tag).toEqual(expect.objectContaining({ value: 'tagged' }));
85+
expect(data).not.toHaveProperty('tag');
8786
});
8887

8988
it('throws when not connected', async () => {
@@ -141,6 +140,24 @@ describe('ResoniteLinkClient', () => {
141140
expect(payload.rotation?.value.w).toBeCloseTo(0, 5);
142141
});
143142

143+
it('updateSlot updates tag field when provided', async () => {
144+
await client.connect();
145+
mockLink.slotUpdate.mockResolvedValue({ success: true });
146+
147+
await client.updateSlot({
148+
id: 'slot-tag',
149+
tag: 'import-root',
150+
});
151+
152+
expect(mockLink.slotUpdate).toHaveBeenCalledTimes(1);
153+
const [slotId, payload] = mockLink.slotUpdate.mock.calls[0] as [
154+
string,
155+
{ tag?: { value: string } },
156+
];
157+
expect(slotId).toBe('slot-tag');
158+
expect(payload.tag).toEqual(expect.objectContaining({ value: 'import-root' }));
159+
});
160+
144161
it('passes ws.WebSocket constructor to tsrl connect', async () => {
145162
await client.connect();
146163
expect(connectMock).toHaveBeenCalledTimes(1);

src/resonite/ResoniteLinkClient.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,6 @@ interface RawAddSlotMessage {
6363
rotation: RawFieldValue<Quaternion>;
6464
isActive: RawFieldValue<boolean>;
6565
isPersistent: RawFieldValue<boolean>;
66-
tag: RawFieldValue<string>;
6766
orderOffset: RawFieldValue<number>;
6867
};
6968
}
@@ -130,7 +129,6 @@ function toTsrlClientMessage(message: RawClientMessage): ClientMessage {
130129
rotation: message.data.rotation,
131130
isActive: message.data.isActive,
132131
isPersistent: message.data.isPersistent,
133-
tag: message.data.tag,
134132
orderOffset: message.data.orderOffset,
135133
},
136134
};
@@ -273,7 +271,6 @@ export class ResoniteLinkClient {
273271
rotation?: Quaternion;
274272
scale?: Vector3;
275273
isActive?: boolean;
276-
tag?: string;
277274
}): Promise<string> {
278275
const scale = options.scale ?? { x: 1, y: 1, z: 1 };
279276
const rotation = options.rotation ?? { x: 0, y: 0, z: 0, w: 1 };
@@ -288,7 +285,6 @@ export class ResoniteLinkClient {
288285
rotation: createField(rotation),
289286
isActive: createField(options.isActive ?? true),
290287
isPersistent: createField(true),
291-
tag: createField(options.tag ?? ''),
292288
orderOffset: createField(0),
293289
},
294290
};
@@ -305,6 +301,7 @@ export class ResoniteLinkClient {
305301
position?: Vector3;
306302
rotation?: Vector3;
307303
scale?: Vector3;
304+
tag?: string;
308305
}): Promise<void> {
309306
const link = this.getConnectedLink();
310307

@@ -318,6 +315,9 @@ export class ResoniteLinkClient {
318315
if (options.scale) {
319316
data.scale = createField(options.scale);
320317
}
318+
if (typeof options.tag === 'string') {
319+
data.tag = createField(options.tag);
320+
}
321321

322322
if (Object.keys(data).length === 0) {
323323
return;

src/resonite/SlotBuilder.test.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -985,6 +985,17 @@ describe('SlotBuilder', () => {
985985
});
986986
});
987987

988+
describe('tagImportGroupRoot', () => {
989+
it('should apply import root tag to specified slot', async () => {
990+
await slotBuilder.tagImportGroupRoot('group-slot-id');
991+
992+
expect(mockClient.updateSlot).toHaveBeenCalledWith({
993+
id: 'group-slot-id',
994+
tag: IMPORT_ROOT_TAG,
995+
});
996+
});
997+
});
998+
988999
describe('createImportGroup', () => {
9891000
it('should create a group slot with UUID-based ID', async () => {
9901001
await slotBuilder.createImportGroup('My Import');
@@ -996,7 +1007,6 @@ describe('SlotBuilder', () => {
9961007
position: { x: 0, y: IMPORT_GROUP_Y_OFFSET, z: 0 },
9971008
rotation: { x: 0, y: 1, z: 0, w: 0 },
9981009
scale: { x: IMPORT_GROUP_SCALE, y: IMPORT_GROUP_SCALE, z: IMPORT_GROUP_SCALE },
999-
tag: IMPORT_ROOT_TAG,
10001010
})
10011011
);
10021012

@@ -1014,6 +1024,7 @@ describe('SlotBuilder', () => {
10141024
componentType: COMPONENT_TYPES.SIMPLE_AVATAR_PROTECTION,
10151025
fields: {},
10161026
});
1027+
expect(mockClient.updateSlot).not.toHaveBeenCalled();
10171028
});
10181029

10191030
it('should return the group slot ID', async () => {

0 commit comments

Comments
 (0)