UNPKG

reldens

Version:
328 lines (265 loc) 10.8 kB
# Room Images and Tileset Override System ## Overview This document explains how the room scene images upload system works and how the `overrideSceneImagesWithMapFile` option automatically synchronizes scene images with the Tiled map file tilesets. ## Configuration **Config Path:** `server/rooms/maps/overrideSceneImagesWithMapFile` **Type:** Boolean **Default:** `true` **Location:** Database `config` table or environment variable When enabled, the system uses the Tiled map file as the source of truth for scene images, automatically overriding the `scene_images` field with images listed in the map's tilesets. ## File Locations ### Source Code - **Validator:** `lib/admin/server/room-map-tilesets-validator.js` - **Subscriber:** `lib/admin/server/subscribers/rooms-entity-subscriber.js` - **File Upload Renderer:** `lib/admin/server/rooms-file-upload-renderer.js` - **Admin Plugin:** `lib/admin/server/plugin.js` ### Admin Interface - **Tileset File Item Template:** `theme/admin/templates/fields/edit/tileset-file-item.html` - **Tileset Alert Wrapper Template:** `theme/admin/templates/fields/edit/tileset-alert-wrapper.html` - **Client JS:** `theme/admin/reldens-admin-client.js` - **Client CSS:** `theme/admin/reldens-admin-client.css` - **Router:** `npm-packages/reldens-cms/lib/admin-manager/router-contents.js` ## Database Schema ### Rooms Table - `id` - Room identifier - `map_filename` - Tiled map JSON file (e.g., `reldens-forest.json`) - `scene_images` - Comma-separated list of tileset images (e.g., `reldens-forest.png,reldens-town.png`) ### Upload Configuration Both fields are configured as upload fields: - `map_filename` - Single file upload, bucket: `theme/assets/maps` - `scene_images` - Multiple file upload, bucket: `theme/assets/images` ## System Flow ### 1. Initial Room Creation **User Actions:** 1. Navigate to Admin → Rooms → Create New 2. Upload map JSON file to `map_filename` field 3. Upload tileset images to `scene_images` field 4. Click Save **System Processing:** 1. **Upload Phase** - Files saved to respective buckets 2. **Validation Phase** - `validateUploadedFiles()` checks required fields 3. **Save Phase** - Entity created in database 4. **Post-Save Event** - `reldens.adminAfterEntitySave` fires 5. **Validator Execution** - `RoomMapTilesetsValidator.validate()` runs **Validator Logic:** ```javascript // Check if override is enabled overrideEnabled = config.getWithoutLogs('server/rooms/maps/overrideSceneImagesWithMapFile', true) // Read map file mapData = readMapFile(bucket, mapFilename, roomId) // Extract tileset images from map JSON tilesetImages = extractTilesetImages(mapData.tilesets) // Example: ['reldens-forest.png'] // Compare with current scene_images if (tilesetImages !== currentSceneImages) { // Validate all images exist in scene_images bucket if (validateImagesExist(tilesetImages, sceneImagesBucket)) { // Override scene_images with tileset images roomsRepository.updateById(roomId, {scene_images: tilesetImages.join(',')}) } } ``` ### 2. Room Editing **User Actions:** 1. Navigate to Admin → Rooms → Edit Room 2. View existing files in both fields 3. Modify files or click Save without changes **Edit Form Population:** **Event:** `reldens.adminEditPropertiesPopulation` **Flow:** ```javascript // 1. Event emitted with room data event = { driverResource, // Entity configuration renderedEditProperties, // Form properties loadedEntity, // Room from database entityId: 'rooms', entityData: loadedEntity } // 2. RoomsEntitySubscriber.populateEditFormTilesetImages() executes if (overrideSceneImagesWithMapFile) { // Extract tileset images from map file tilesetImages = validator.extractTilesetImagesFromEntity(entityData, driverResource) // Inject into form properties renderedEditProperties.tilesetImages = tilesetImages renderedEditProperties.overrideSceneImagesEnabled = true } // 3. RoomsFileUploadRenderer processes scene_images field // Event: reldens.adminBeforeFieldRender if (propertyKey === 'scene_images' && tilesetImages.length > 0) { // Render each file with protection flag for each file: renderedFileItems.push(render tileset-file-item.html with { filename, isProtected: tilesetImages.includes(filename) }) // Wrap files in alert container templateData.renderedFiles = render tileset-alert-wrapper.html } // 4. Template renders with tileset protection {{^isProtected}} <button class="remove-upload-btn">X</button> - {{/isProtected}} {{filename}} ``` **Result:** - Protected images (tilesets): NO remove button - Non-protected images: Remove button shown - Alert icon displays with info message ### 3. Saving Changes **Scenario A: No Files Changed** 1. User clicks Save without uploading/removing files 2. Validation passes (existing files satisfy requirement) 3. Entity updated with form data 4. Post-save validator runs 5. If scene_images matches tilesets → No action 6. If mismatch → Override with tileset images **Scenario B: Add New Image** 1. User uploads additional image to `scene_images` 2. `prepareUploadPatchData()` appends new file to existing files 3. Entity saved with: `existing_images.png,new_image.png` 4. Post-save validator runs 5. Validates tileset images exist 6. **Overrides** scene_images with ONLY tileset images (removes non-tileset images) **Scenario C: Remove Non-Protected Image** 1. User clicks X button on non-protected image 2. Client adds filename to `removed_scene_images` hidden input 3. `prepareUploadPatchData()` filters removed files 4. Entity saved with filtered list 5. Post-save validator runs 6. Overrides with tileset images (removes non-tileset files) **Scenario D: Attempt Remove Protected Image (Prevented)** 1. Protected image (tileset) has NO remove button 2. User cannot remove it through UI 3. Alert icon displays: "Images specified in the tileset can't be removed since the option overrideSceneImagesWithMapFile is active." ### 4. Map File Update **User Updates Map File:** 1. User replaces `map_filename` with new Tiled map 2. New map references different tileset images 3. Entity saved 4. Post-save validator executes 5. Reads new map file tilesets 6. **Replaces** scene_images with new tileset images 7. Old images no longer referenced (user must manage cleanup) ## Technical Details ### Map File Structure **Example: reldens-forest.json** ```json { "tilesets": [ { "columns": 14, "firstgid": 1, "image": "reldens-forest.png", "imageheight": 408, "imagewidth": 476, "name": "reldens-forest", "tilecount": 168 } ] } ``` **Extraction Logic:** ```javascript extractTilesetImages(mapData) { let tilesets = mapData.tilesets || [] let images = [] for (let tileset of tilesets) { let tilesetImage = tileset.image // 'reldens-forest.png' or '../images/reldens-forest.png' let imageFileName = tilesetImage.split('/').pop() // Extract filename only if (!images.includes(imageFileName)) { images.push(imageFileName) } } return images // ['reldens-forest.png'] } ``` ### Validation Logic **Array Comparison (validator):** ```javascript arraysAreEqual(array1, array2) { if (array1.length !== array2.length) { return false } let sorted1 = [...array1].sort() let sorted2 = [...array2].sort() for (let i = 0; i < sorted1.length; i++) { if (sorted1[i] !== sorted2[i]) { return false } } return true } ``` **Image Existence Validation (validator):** ```javascript validateImagesExist(tilesetImages, sceneImagesBucket, roomId, mapFilename) { for (let imageFileName of tilesetImages) { let imageFilePath = FileHandler.joinPaths(sceneImagesBucket, imageFileName) if (!FileHandler.exists(imageFilePath)) { return false } } return true } ``` ### Client-Side Protection **File Item Template (tileset-file-item.html):** ```html <p class="upload-current-file" data-field="{{&fieldName}}" data-filename="{{&filename}}"> {{^isProtected}} <button type="button" class="remove-upload-btn" data-field="{{&fieldName}}" data-filename="{{&filename}}" title="REMOVE">X</button> - {{/isProtected}} {{&filename}} </p> ``` **Alert Wrapper Template (tileset-alert-wrapper.html):** ```html <div class="tileset-alert-wrapper"> <div class="upload-files-with-alert"> {{{renderedFileItems}}} </div> <div class="tileset-alert-icon-container"> <img src="/assets/admin/alert.png" class="tileset-alert-icon" alt="Info" title="Images specified in the tileset can't be removed since the option overrideSceneImagesWithMapFile is active."> <span class="tileset-info-message hidden">Images specified in the tileset can't be removed since the option overrideSceneImagesWithMapFile is active.</span> </div> </div> ``` **JavaScript Toggle (reldens-admin-client.js):** ```javascript document.querySelectorAll('.tileset-alert-icon').forEach(icon => { icon.addEventListener('click', () => { let message = icon.nextElementSibling if (message?.classList.contains('tileset-info-message')) { message.classList.toggle('hidden') } }) }) ``` ## Benefits 1. **Consistency:** Scene images always match map tilesets 2. **Automation:** No manual sync between map and images 3. **Single Source of Truth:** Tiled map file controls image references 4. **Developer Experience:** Edit maps in Tiled, changes auto-sync ## Limitations 1. **One-Way Sync:** Map → Database only (not bidirectional) 2. **Cleanup Required:** Removing tileset from map doesn't delete old image files 3. **Override Always Wins:** Manual changes to scene_images get overwritten on next save 4. **Requires Config:** Must enable `overrideSceneImagesWithMapFile` to activate ## Disabling the Feature To disable tileset override and manage images manually: **Option 1: Database Config** ```sql UPDATE config SET value = '0' WHERE path = 'server/rooms/maps/overrideSceneImagesWithMapFile'; ``` **Option 2: Environment Variable** ```bash RELDENS_SERVER_ROOMS_MAPS_OVERRIDESCENEIMAGESWITHMAPFILE=0 ``` **Result:** - Post-save validation skipped - All images show remove buttons - Full manual control over scene_images field - Map file and scene_images can diverge