diff --git a/src/components/street-generated-clones.js b/src/components/street-generated-clones.js index 419f604be..fdd50c511 100644 --- a/src/components/street-generated-clones.js +++ b/src/components/street-generated-clones.js @@ -5,7 +5,6 @@ AFRAME.registerComponent('street-generated-clones', { multiple: true, schema: { // Common properties - model: { type: 'string' }, modelsArray: { type: 'array' }, // For random selection from multiple models length: { type: 'number' }, // length in meters of segment positionX: { default: 0, type: 'number' }, @@ -178,10 +177,8 @@ AFRAME.registerComponent('street-generated-clones', { getModelMixin: function () { const data = this.data; - if (data.modelsArray && data.modelsArray.length > 0) { - return data.modelsArray[Math.floor(this.rng() * data.modelsArray.length)]; - } - return data.model; + if (!this.rng) return data.modelsArray[0]; // this is a hack but it works for now + return data.modelsArray[Math.floor(this.rng() * data.modelsArray.length)]; }, randPlacedElements: function (streetLength, spacing, count) { diff --git a/src/components/street-segment.js b/src/components/street-segment.js index 4bdaed899..9bd406ca5 100644 --- a/src/components/street-segment.js +++ b/src/components/street-segment.js @@ -47,7 +47,7 @@ const TYPES = { clones: [ { mode: 'random', - model: 'bus', + modelsArray: 'bus', spacing: 15, count: 1 } @@ -198,25 +198,14 @@ AFRAME.registerComponent('street-segment', { // for each of clones, stencils, rail, pedestrians, etc. if (componentsToGenerate?.clones?.length > 0) { componentsToGenerate.clones.forEach((clone, index) => { - if (clone?.modelsArray?.length > 0) { - this.el.setAttribute(`street-generated-clones__${index}`, { - mode: clone.mode, - modelsArray: clone.modelsArray, - length: this.data.length, - spacing: clone.spacing, - direction: this.data.direction, - count: clone.count - }); - } else { - this.el.setAttribute(`street-generated-clones__${index}`, { - mode: clone.mode, - model: clone.model, - length: this.data.length, - spacing: clone.spacing, - direction: this.data.direction, - count: clone.count - }); - } + this.el.setAttribute(`street-generated-clones__${index + 1}`, { + mode: clone.mode, + modelsArray: clone.modelsArray, + length: this.data.length, + spacing: clone.spacing, + direction: this.data.direction, + count: clone.count + }); }); } @@ -224,7 +213,7 @@ AFRAME.registerComponent('street-segment', { componentsToGenerate.stencil.forEach((clone, index) => { if (clone?.stencils?.length > 0) { // case where there are multiple stencils such as bus-only - this.el.setAttribute(`street-generated-stencil__${index}`, { + this.el.setAttribute(`street-generated-stencil__${index + 1}`, { stencils: clone.stencils, length: this.data.length, spacing: clone.spacing, @@ -233,7 +222,7 @@ AFRAME.registerComponent('street-segment', { cycleOffset: clone.cycleOffset }); } else { - this.el.setAttribute(`street-generated-stencil__${index}`, { + this.el.setAttribute(`street-generated-stencil__${index + 1}`, { model: clone.model, length: this.data.length, spacing: clone.spacing, @@ -247,7 +236,7 @@ AFRAME.registerComponent('street-segment', { if (componentsToGenerate?.pedestrians?.length > 0) { componentsToGenerate.pedestrians.forEach((pedestrian, index) => { - this.el.setAttribute(`street-generated-pedestrians__${index}`, { + this.el.setAttribute(`street-generated-pedestrians__${index + 1}`, { segmentWidth: this.data.width, density: pedestrian.density, length: this.data.length, @@ -258,7 +247,7 @@ AFRAME.registerComponent('street-segment', { if (componentsToGenerate?.striping?.length > 0) { componentsToGenerate.striping.forEach((stripe, index) => { - this.el.setAttribute(`street-generated-striping__${index}`, { + this.el.setAttribute(`street-generated-striping__${index + 1}`, { striping: stripe.striping, segmentWidth: this.data.width, length: this.data.length, diff --git a/src/editor/components/components/AddLayerPanel/defaultStreets.js b/src/editor/components/components/AddLayerPanel/defaultStreets.js index 76b8a882f..c91dd0dd3 100644 --- a/src/editor/components/components/AddLayerPanel/defaultStreets.js +++ b/src/editor/components/components/AddLayerPanel/defaultStreets.js @@ -1,7 +1,6 @@ // Define some example streets in Managed Street object format export const stroad60ftROW = { - id: '2d729802-6d80-45fa-89bd-f6d6b120d936', name: '60ft Right of Way 36ft Road Width', width: 18.288, // Keep in meters length: 100, @@ -9,7 +8,6 @@ export const stroad60ftROW = { justifyLength: 'start', segments: [ { - id: 'JCWzsLQHmyfDHzQhi9_pU', name: 'Dense Sidewalk', type: 'sidewalk', surface: 'sidewalk', @@ -26,7 +24,6 @@ export const stroad60ftROW = { } }, { - id: 'RsLZFtSi3oJH7uufQ5rc4', name: 'Tree Planting Strip', type: 'sidewalk', surface: 'sidewalk', @@ -38,14 +35,13 @@ export const stroad60ftROW = { clones: [ { mode: 'fixed', - model: 'tree3', + modelsArray: 'tree3', spacing: 15 } ] } }, { - id: 'Xf2CNmHkMaGkTM8EaJn6h', name: 'Modern Street Lamp', type: 'sidewalk', surface: 'sidewalk', @@ -57,7 +53,7 @@ export const stroad60ftROW = { clones: [ { mode: 'fixed', - model: 'lamp-modern', + modelsArray: 'lamp-modern', spacing: 30, facing: 0 } @@ -65,7 +61,6 @@ export const stroad60ftROW = { } }, { - id: 'GbEHhCMPmVom_IJK-xIn3', name: 'Inbound Parking', type: 'parking-lane', surface: 'concrete', @@ -92,7 +87,6 @@ export const stroad60ftROW = { } }, { - id: 'z4gZgzYoM7sQ7mzIV01PC', name: 'Inbound Drive Lane', type: 'drive-lane', surface: 'asphalt', @@ -113,7 +107,6 @@ export const stroad60ftROW = { } }, { - id: 'myp8_d3x_-hwuhTyH8ux1', name: 'Outbound Drive Lane', type: 'drive-lane', surface: 'asphalt', @@ -134,7 +127,6 @@ export const stroad60ftROW = { } }, { - id: 'ARosTXeWGXp17QyfZgSKB', name: 'Outbound Parking', type: 'parking-lane', surface: 'concrete', @@ -161,7 +153,6 @@ export const stroad60ftROW = { } }, { - id: 'oweuZgwBHUbt65Ep7GZhU', name: 'Modern Street Lamp', type: 'sidewalk', surface: 'sidewalk', @@ -173,7 +164,7 @@ export const stroad60ftROW = { clones: [ { mode: 'fixed', - model: 'lamp-modern', + modelsArray: 'lamp-modern', spacing: 30, facing: 180 } @@ -181,7 +172,6 @@ export const stroad60ftROW = { } }, { - id: 'vL9qDNp5neZt32zlZ9ExG', name: 'Tree Planting Strip', type: 'sidewalk', surface: 'sidewalk', @@ -193,14 +183,13 @@ export const stroad60ftROW = { clones: [ { mode: 'fixed', - model: 'tree3', + modelsArray: 'tree3', spacing: 15 } ] } }, { - id: 'RClRRZoof9_BYnqQm7mz-', name: 'Normal Sidewalk', type: 'sidewalk', surface: 'sidewalk', @@ -219,112 +208,7 @@ export const stroad60ftROW = { ] }; -export const exampleStreet = { - id: 'aaaaaaaa-0123-4678-9000-000000000000', - name: "Kieran's Basic Street", - width: 40, - length: 100, - justifyWidth: 'center', - justifyLength: 'start', - segments: [ - { - id: 'aaaaaaaa-0123-4678-9000-000000000001', - name: 'Sidewalk for walking', - type: 'sidewalk', - surface: 'sidewalk', - color: '#ffffff', - level: 1, - width: 3, - direction: 'none', - generated: { - pedestrians: [ - { - density: 'normal' - } - ] - } - }, - { - id: 'aaaaaaaa-0123-4678-9000-000000000002', - name: 'Sidewalk for trees and stuff', - type: 'sidewalk', - surface: 'sidewalk', - color: '#ffffff', - level: 1, - width: 1, - direction: 'none', - generated: { - clones: [ - { - mode: 'fixed', - model: 'tree3', - spacing: 15 - } - ] - } - }, - { - id: 'aaaaaaaa-0123-4678-9000-000000000003', - name: 'Parking for cars', - type: 'parking-lane', - surface: 'concrete', - color: '#dddddd', - level: 0, - width: 3, - direction: 'inbound', - generated: { - clones: [ - { - mode: 'random', - modelsArray: 'sedan-rig, self-driving-waymo-car, suv-rig', - spacing: 6, - count: 6 - } - ], - stencil: [ - { - model: 'parking-t', - cycleOffset: 1, - spacing: 6 - } - ] - } - }, - { - id: 'aaaaaaaa-0123-4678-9000-000000000004', - name: 'Drive Lane for cars and stuff', - type: 'drive-lane', - color: '#ffffff', - surface: 'asphalt', - level: 0, - width: 3, - direction: 'inbound', - generated: { - clones: [ - { - mode: 'random', - modelsArray: - 'sedan-rig, box-truck-rig, self-driving-waymo-car, suv-rig, motorbike', - spacing: 7.3, - count: 4 - } - ] - } - }, - { - id: 'aaaaaaaa-0123-4678-9000-000000000005', - name: 'A beautiful median', - type: 'divider', - surface: 'sidewalk', - color: '#ffffff', - level: 1, - width: 0.5 - } - ] -}; - export const stroad40ftROW = { - id: '727dbbf4-692a-48ee-8f99-a056fd60fedd', name: '40ft Right of Way 24ft Road Width', width: 12.192, // Original 40ft converted to meters length: 100, @@ -332,7 +216,6 @@ export const stroad40ftROW = { justifyLength: 'start', segments: [ { - id: 'JCWzsLQHmyfDHzQhi9_pU', name: 'Dense Sidewalk', type: 'sidewalk', surface: 'sidewalk', @@ -349,7 +232,6 @@ export const stroad40ftROW = { } }, { - id: 'RsLZFtSi3oJH7uufQ5rc4', name: 'Tree Planting Strip', type: 'sidewalk', surface: 'sidewalk', @@ -361,14 +243,13 @@ export const stroad40ftROW = { clones: [ { mode: 'fixed', - model: 'tree3', + modelsArray: 'tree3', spacing: 15 } ] } }, { - id: 'GbEHhCMPmVom_IJK-xIn3', name: 'Inbound Parking', type: 'parking-lane', surface: 'concrete', @@ -395,7 +276,6 @@ export const stroad40ftROW = { } }, { - id: 'z4gZgzYoM7sQ7mzIV01PC', name: 'Drive Lane', type: 'drive-lane', surface: 'asphalt', @@ -416,7 +296,6 @@ export const stroad40ftROW = { } }, { - id: 'ARosTXeWGXp17QyfZgSKB', name: 'Outbound Parking', type: 'parking-lane', surface: 'concrete', @@ -443,7 +322,6 @@ export const stroad40ftROW = { } }, { - id: 'vL9qDNp5neZt32zlZ9ExG', name: 'Tree Planting Strip', type: 'sidewalk', surface: 'sidewalk', @@ -455,14 +333,13 @@ export const stroad40ftROW = { clones: [ { mode: 'fixed', - model: 'tree3', + modelsArray: 'tree3', spacing: 15 } ] } }, { - id: 'RClRRZoof9_BYnqQm7mz-', name: 'Normal Sidewalk', type: 'sidewalk', surface: 'sidewalk', @@ -482,7 +359,6 @@ export const stroad40ftROW = { }; export const stroad80ftROW = { - id: 'dea1980d-2a13-481b-b318-9f757ca114f7', name: '80ft Right of Way 56ft Road Width', width: 24.384, // Original 80ft converted to meters length: 100, @@ -490,7 +366,6 @@ export const stroad80ftROW = { justifyLength: 'start', segments: [ { - id: 'JCWzsLQHmyfDHzQhi9_pU', name: 'Dense Sidewalk', type: 'sidewalk', surface: 'sidewalk', @@ -507,7 +382,6 @@ export const stroad80ftROW = { } }, { - id: 'RsLZFtSi3oJH7uufQ5rc4', name: 'Tree Planting Strip', type: 'sidewalk', surface: 'sidewalk', @@ -519,14 +393,13 @@ export const stroad80ftROW = { clones: [ { mode: 'fixed', - model: 'tree3', + modelsArray: 'tree3', spacing: 15 } ] } }, { - id: 'X2tAKuwUDc728RIPfhJUS', name: 'Modern Street Lamp', type: 'sidewalk', surface: 'sidewalk', @@ -538,7 +411,7 @@ export const stroad80ftROW = { clones: [ { mode: 'fixed', - model: 'lamp-modern', + modelsArray: 'lamp-modern', spacing: 30, facing: 0 } @@ -546,7 +419,6 @@ export const stroad80ftROW = { } }, { - id: 'GbEHhCMPmVom_IJK-xIn3', name: 'Inbound Parking', type: 'parking-lane', surface: 'concrete', @@ -573,7 +445,6 @@ export const stroad80ftROW = { } }, { - id: 'z4gZgzYoM7sQ7mzIV01PC', name: 'Inbound Drive Lane 1', type: 'drive-lane', surface: 'asphalt', @@ -594,7 +465,6 @@ export const stroad80ftROW = { } }, { - id: 'n9A8XDtjRSpgxElVhxoWB', name: 'Inbound Drive Lane 2', type: 'drive-lane', surface: 'asphalt', @@ -615,7 +485,6 @@ export const stroad80ftROW = { } }, { - id: 'O08G5Br9w6vwdomdhUmwk', name: 'Outbound Drive Lane 1', type: 'drive-lane', surface: 'asphalt', @@ -636,7 +505,6 @@ export const stroad80ftROW = { } }, { - id: '1w9jjehQwnvBfJeSVOd6M', name: 'Outbound Drive Lane 2', type: 'drive-lane', surface: 'asphalt', @@ -657,7 +525,6 @@ export const stroad80ftROW = { } }, { - id: 'ARosTXeWGXp17QyfZgSKB', name: 'Outbound Parking', type: 'parking-lane', surface: 'concrete', @@ -684,7 +551,6 @@ export const stroad80ftROW = { } }, { - id: '2p_cReSRF4748HV9Fyejr', name: 'Modern Street Lamp', type: 'sidewalk', surface: 'sidewalk', @@ -696,7 +562,7 @@ export const stroad80ftROW = { clones: [ { mode: 'fixed', - model: 'lamp-modern', + modelsArray: 'lamp-modern', spacing: 30, facing: 180 } @@ -704,7 +570,6 @@ export const stroad80ftROW = { } }, { - id: 'vL9qDNp5neZt32zlZ9ExG', name: 'Tree Planting Strip', type: 'sidewalk', surface: 'sidewalk', @@ -716,14 +581,13 @@ export const stroad80ftROW = { clones: [ { mode: 'fixed', - model: 'tree3', + modelsArray: 'tree3', spacing: 15 } ] } }, { - id: 'RClRRZoof9_BYnqQm7mz-', name: 'Normal Sidewalk', type: 'sidewalk', surface: 'sidewalk', @@ -743,7 +607,6 @@ export const stroad80ftROW = { }; export const stroad94ftROW = { - id: 'a55d288c-215d-49a2-b67f-3efb9ec9ff41', name: '94ft Right of Way 70ft Road Width', width: 28.651, // Original 94ft converted to meters length: 100, @@ -751,7 +614,6 @@ export const stroad94ftROW = { justifyLength: 'start', segments: [ { - id: 'JCWzsLQHmyfDHzQhi9_pU', name: 'Dense Sidewalk', type: 'sidewalk', surface: 'sidewalk', @@ -768,7 +630,6 @@ export const stroad94ftROW = { } }, { - id: 'RsLZFtSi3oJH7uufQ5rc4', name: 'Tree Planting Strip', type: 'sidewalk', surface: 'sidewalk', @@ -780,14 +641,13 @@ export const stroad94ftROW = { clones: [ { mode: 'fixed', - model: 'tree3', + modelsArray: 'tree3', spacing: 15 } ] } }, { - id: 'X2tAKuwUDc728RIPfhJUS', name: 'Modern Street Lamp', type: 'sidewalk', surface: 'sidewalk', @@ -799,7 +659,7 @@ export const stroad94ftROW = { clones: [ { mode: 'fixed', - model: 'lamp-modern', + modelsArray: 'lamp-modern', spacing: 30, facing: 0 } @@ -807,7 +667,6 @@ export const stroad94ftROW = { } }, { - id: 'GbEHhCMPmVom_IJK-xIn3', name: 'Inbound Parking', type: 'parking-lane', surface: 'concrete', @@ -834,7 +693,6 @@ export const stroad94ftROW = { } }, { - id: 'z4gZgzYoM7sQ7mzIV01PC', name: 'Inbound Drive Lane 1', type: 'drive-lane', surface: 'asphalt', @@ -855,7 +713,6 @@ export const stroad94ftROW = { } }, { - id: 'n9A8XDtjRSpgxElVhxoWB', name: 'Inbound Drive Lane 2', type: 'drive-lane', surface: 'asphalt', @@ -876,7 +733,6 @@ export const stroad94ftROW = { } }, { - id: 'zUl55HA-DUaJpyQEelUhW', name: 'Center Turn Lane', type: 'drive-lane', surface: 'asphalt', @@ -910,7 +766,6 @@ export const stroad94ftROW = { } }, { - id: 'O08G5Br9w6vwdomdhUmwk', name: 'Outbound Drive Lane 1', type: 'drive-lane', surface: 'asphalt', @@ -936,7 +791,6 @@ export const stroad94ftROW = { } }, { - id: '1w9jjehQwnvBfJeSVOd6M', name: 'Outbound Drive Lane 2', type: 'drive-lane', surface: 'asphalt', @@ -957,7 +811,6 @@ export const stroad94ftROW = { } }, { - id: 'ARosTXeWGXp17QyfZgSKB', name: 'Outbound Parking', type: 'parking-lane', surface: 'concrete', @@ -984,7 +837,6 @@ export const stroad94ftROW = { } }, { - id: '2p_cReSRF4748HV9Fyejr', name: 'Modern Street Lamp', type: 'sidewalk', surface: 'sidewalk', @@ -996,7 +848,7 @@ export const stroad94ftROW = { clones: [ { mode: 'fixed', - model: 'lamp-modern', + modelsArray: 'lamp-modern', spacing: 30, facing: 180 } @@ -1004,7 +856,6 @@ export const stroad94ftROW = { } }, { - id: 'vL9qDNp5neZt32zlZ9ExG', name: 'Tree Planting Strip', type: 'sidewalk', surface: 'sidewalk', @@ -1016,14 +867,13 @@ export const stroad94ftROW = { clones: [ { mode: 'fixed', - model: 'tree3', + modelsArray: 'tree3', spacing: 15 } ] } }, { - id: 'RClRRZoof9_BYnqQm7mz-', name: 'Normal Sidewalk', type: 'sidewalk', surface: 'sidewalk', @@ -1043,7 +893,6 @@ export const stroad94ftROW = { }; export const stroad150ftROW = { - id: 'f8eeb25c-f68c-4f0b-9435-3f87e6be705a', name: '150ft Right of Way 124ft Road Width', width: 45.72, // Original 150ft converted to meters length: 100, @@ -1051,7 +900,6 @@ export const stroad150ftROW = { justifyLength: 'start', segments: [ { - id: 'JCWzsLQHmyfDHzQhi9_pU', name: 'Dense Sidewalk', type: 'sidewalk', surface: 'sidewalk', @@ -1068,7 +916,6 @@ export const stroad150ftROW = { } }, { - id: 'RsLZFtSi3oJH7uufQ5rc4', name: 'Tree Planting Strip', type: 'sidewalk', surface: 'sidewalk', @@ -1080,14 +927,13 @@ export const stroad150ftROW = { clones: [ { mode: 'fixed', - model: 'tree3', + modelsArray: 'tree3', spacing: 15 } ] } }, { - id: 'X2tAKuwUDc728RIPfhJUS', name: 'Modern Street Lamp', type: 'sidewalk', surface: 'sidewalk', @@ -1099,7 +945,7 @@ export const stroad150ftROW = { clones: [ { mode: 'fixed', - model: 'lamp-modern', + modelsArray: 'lamp-modern', spacing: 30, facing: 0 } @@ -1107,7 +953,6 @@ export const stroad150ftROW = { } }, { - id: 'GbEHhCMPmVom_IJK-xIn3', name: 'Inbound Parking', type: 'parking-lane', surface: 'concrete', @@ -1134,7 +979,6 @@ export const stroad150ftROW = { } }, { - id: 'vLBLQS2VraoTL2sJRmD4J', name: 'Inbound Left Turn Lane', type: 'drive-lane', surface: 'asphalt', @@ -1161,7 +1005,6 @@ export const stroad150ftROW = { } }, { - id: 'z4gZgzYoM7sQ7mzIV01PC', name: 'Inbound Drive Lane 1', type: 'drive-lane', surface: 'asphalt', @@ -1182,7 +1025,6 @@ export const stroad150ftROW = { } }, { - id: '3a42u-6x8OsoGsI7bjb4z', name: 'Inbound Truck Lane', type: 'drive-lane', surface: 'asphalt', @@ -1202,7 +1044,6 @@ export const stroad150ftROW = { } }, { - id: 'n9A8XDtjRSpgxElVhxoWB', name: 'Inbound Drive Lane 2', type: 'drive-lane', surface: 'asphalt', @@ -1223,7 +1064,6 @@ export const stroad150ftROW = { } }, { - id: 'E1UvC71Nkre2H2-hg2gMd', name: 'Inbound Right Turn Lane', type: 'drive-lane', surface: 'asphalt', @@ -1250,7 +1090,6 @@ export const stroad150ftROW = { } }, { - id: 'WisaQ2Pfc5K51O8k_Mrnb', name: 'Planted Median', type: 'divider', surface: 'planting-strip', @@ -1262,14 +1101,13 @@ export const stroad150ftROW = { clones: [ { mode: 'fixed', - model: 'flowers1', + modelsArray: 'flowers1', spacing: 3 } ] } }, { - id: 'qvQftgSPmiA7afQles5EK', name: 'Outbound Left Turn Lane', type: 'drive-lane', surface: 'asphalt', @@ -1296,7 +1134,6 @@ export const stroad150ftROW = { } }, { - id: 'O08G5Br9w6vwdomdhUmwk', name: 'Outbound Drive Lane 1', type: 'drive-lane', surface: 'asphalt', @@ -1317,7 +1154,6 @@ export const stroad150ftROW = { } }, { - id: '1w9jjehQwnvBfJeSVOd6M', name: 'Outbound Truck Lane', type: 'drive-lane', surface: 'asphalt', @@ -1337,7 +1173,6 @@ export const stroad150ftROW = { } }, { - id: 'RnfgVJLk2oJv7QTW_s3WR', name: 'Outbound Drive Lane 2', type: 'drive-lane', surface: 'asphalt', @@ -1358,7 +1193,6 @@ export const stroad150ftROW = { } }, { - id: 'va_9kr_Dtr9q8ddlzAl02', name: 'Outbound Right Turn Lane', type: 'drive-lane', surface: 'asphalt', @@ -1385,7 +1219,6 @@ export const stroad150ftROW = { } }, { - id: 'ARosTXeWGXp17QyfZgSKB', name: 'Outbound Parking', type: 'parking-lane', surface: 'concrete', @@ -1412,7 +1245,6 @@ export const stroad150ftROW = { } }, { - id: '2p_cReSRF4748HV9Fyejr', name: 'Modern Street Lamp', type: 'sidewalk', surface: 'sidewalk', @@ -1424,7 +1256,7 @@ export const stroad150ftROW = { clones: [ { mode: 'fixed', - model: 'lamp-modern', + modelsArray: 'lamp-modern', spacing: 30, facing: 180 } @@ -1432,7 +1264,6 @@ export const stroad150ftROW = { } }, { - id: 'vL9qDNp5neZt32zlZ9ExG', name: 'Tree Planting Strip', type: 'sidewalk', surface: 'sidewalk', @@ -1444,14 +1275,13 @@ export const stroad150ftROW = { clones: [ { mode: 'fixed', - model: 'tree3', + modelsArray: 'tree3', spacing: 15 } ] } }, { - id: 'RClRRZoof9_BYnqQm7mz-', name: 'Normal Sidewalk', type: 'sidewalk', surface: 'sidewalk', diff --git a/src/editor/components/components/PropertyRow.js b/src/editor/components/components/PropertyRow.js index 574e1e779..fda4b898a 100644 --- a/src/editor/components/components/PropertyRow.js +++ b/src/editor/components/components/PropertyRow.js @@ -72,7 +72,13 @@ export default class PropertyRow extends React.Component { }; if (props.schema.oneOf && props.schema.oneOf.length > 0) { - return ; + return ( + + ); } if (type === 'map' || isMap) { return ; @@ -111,7 +117,7 @@ export default class PropertyRow extends React.Component { // Allow editing a custom type like event-set component schema widgetProps.value = props.schema.stringify(widgetProps.value); } - return ; + return ; } } } diff --git a/src/editor/components/components/Sidebar.js b/src/editor/components/components/Sidebar.js index 16edf5704..28556446c 100644 --- a/src/editor/components/components/Sidebar.js +++ b/src/editor/components/components/Sidebar.js @@ -6,7 +6,7 @@ import { import { Button } from '../components'; import ComponentsContainer from './ComponentsContainer'; import Events from '../../lib/Events'; -import Mixins from './Mixins'; +import Mixins from '../widgets/Mixins'; import PropTypes from 'prop-types'; import React from 'react'; import capitalize from 'lodash-es/capitalize'; diff --git a/src/editor/components/components/StreetSegmentComponent.js b/src/editor/components/components/StreetSegmentComponent.js new file mode 100644 index 000000000..ac7d1ad6b --- /dev/null +++ b/src/editor/components/components/StreetSegmentComponent.js @@ -0,0 +1,330 @@ +import Clipboard from 'clipboard'; +import Collapsible from '../Collapsible'; +import Events from '../../lib/Events'; +import PropTypes from 'prop-types'; +import PropertyRow from './PropertyRow'; +import React from 'react'; +import { getComponentClipboardRepresentation } from '../../lib/entity'; +import { ClonedTreesIcon, StencilsIcon, StripingIcon } from '../../icons'; +import ModelsArrayWidget from '../widgets/ModelsArrayWidget'; + +const isSingleProperty = AFRAME.schema.isSingleProperty; + +/** + * Single component. + */ +export default class Component extends React.Component { + static propTypes = { + component: PropTypes.any, + entity: PropTypes.object, + isCollapsed: PropTypes.bool, + name: PropTypes.string + }; + + constructor(props) { + super(props); + this.state = { + entity: this.props.entity, + name: this.props.name + }; + } + + onEntityUpdate = (detail) => { + if (detail.entity !== this.props.entity) { + return; + } + if (detail.component === this.props.name) { + this.forceUpdate(); + } + }; + + componentDidMount() { + var clipboard = new Clipboard( + '[data-action="copy-component-to-clipboard"]', + { + text: (trigger) => { + var componentName = trigger + .getAttribute('data-component') + .toLowerCase(); + return getComponentClipboardRepresentation( + this.state.entity, + componentName + ); + } + } + ); + clipboard.on('error', (e) => { + // @todo Show the error in the UI + console.error(e); + }); + + Events.on('entityupdate', this.onEntityUpdate); + } + + componentWillUnmount() { + Events.off('entityupdate', this.onEntityUpdate); + } + + static getDerivedStateFromProps(props, state) { + if (state.entity !== props.entity) { + return { entity: props.entity }; + } + if (state.name !== props.name) { + return { name: props.name }; + } + return null; + } + + removeComponent = (event) => { + var componentName = this.props.name; + event.stopPropagation(); + if ( + confirm('Do you really want to remove component `' + componentName + '`?') + ) { + AFRAME.INSPECTOR.execute('componentremove', { + entity: this.props.entity, + component: componentName + }); + } + }; + + /** + * Render propert(ies) of the component. + */ + renderPropertyRows = () => { + const componentData = this.props.component; + const componentName = this.props.name; + const schema = AFRAME.components[componentName.split('__')[0]].schema; + + if (componentName.startsWith('street-generated-clones')) { + // Custom rendering for clones + return ( + <> + + + {componentData.data.mode === 'fixed' && ( + <> + + + + )} + {componentData.data.mode === 'random' && ( + <> + + + + )} + {componentData.data.mode === 'single' && ( + <> + + + + )} +
+ + + + + + ); + } + + if (isSingleProperty(schema)) { + return ( + + ); + } + + return Object.keys(componentData.schema) + .sort() + .map((propertyName, idx) => ( +
+ +
+ )); + }; + + getIcon = () => { + const componentName = this.props.name; + if (componentName.startsWith('street-generated-clones')) { + return ; + } else if (componentName.startsWith('street-generated-stencil')) { + return ; + } else if (componentName.startsWith('street-generated-striping')) { + return ; + } + return <>; + }; + + getDisplayName(componentName) { + // Prefix mapping configuration + const PREFIX_MAPPING = { + 'street-generated-clones': 'Clones', + 'street-generated-striping': 'Striping', + 'street-generated-stencil': 'Stencils' + }; + // First check if any prefix mapping matches + for (const [prefix, displayName] of Object.entries(PREFIX_MAPPING)) { + if (componentName.startsWith(prefix)) { + // Get the suffix part (after __) if it exists + const suffixPart = componentName.split('__')[1]; + // Only add suffix if it's not '1' + return suffixPart && suffixPart !== '1' + ? `${displayName} ${suffixPart}` + : displayName; + } + } + + // If no prefix mapping matches, fall back to the original __ splitting behavior + const parts = componentName.split('__'); + return parts[1] && parts[1] !== '1' ? `${parts[0]} ${parts[1]}` : parts[0]; + } + + render() { + const componentName = this.props.name; + const componentDisplayName = this.getDisplayName(componentName); + + return ( + +
+ + {this.getIcon()} + {componentDisplayName} + + +
+
{this.renderPropertyRows()}
+
+ ); + } +} diff --git a/src/editor/components/components/StreetSegmentSidebar.js b/src/editor/components/components/StreetSegmentSidebar.js index 2deb8c011..b48c83803 100644 --- a/src/editor/components/components/StreetSegmentSidebar.js +++ b/src/editor/components/components/StreetSegmentSidebar.js @@ -1,11 +1,23 @@ import PropTypes from 'prop-types'; +// import Component from './Component'; +import Component from './StreetSegmentComponent'; import PropertyRow from './PropertyRow'; import { StreetSurfaceIcon } from '../../icons'; +// Define featured component prefixes that should be shown in their own section +const FEATURED_COMPONENT_PREFIXES = ['street-generated-']; + const StreetSegmentSidebar = ({ entity }) => { const componentName = 'street-segment'; - // Check if entity and its components exist const component = entity?.components?.[componentName]; + const components = entity ? entity.components : {}; + + // Filter for featured components that exist on this entity + const featuredComponents = Object.keys(components).filter((key) => + FEATURED_COMPONENT_PREFIXES.some((prefix) => key.startsWith(prefix)) + ); + + console.log('featuredComponents', featuredComponents); return (
@@ -88,6 +100,23 @@ const StreetSegmentSidebar = ({ entity }) => {
+ + {/* Featured Components section */} + {featuredComponents.length > 0 && ( + <> + {featuredComponents.map((key) => ( +
+ +
+ ))} + + )} )} diff --git a/src/editor/components/widgets/InputWidget.js b/src/editor/components/widgets/InputWidget.js index 6d99cb212..0deb2522a 100644 --- a/src/editor/components/widgets/InputWidget.js +++ b/src/editor/components/widgets/InputWidget.js @@ -7,7 +7,8 @@ export default class InputWidget extends React.Component { entity: PropTypes.object, name: PropTypes.string.isRequired, onChange: PropTypes.func, - value: PropTypes.any + value: PropTypes.any, + schema: PropTypes.object }; constructor(props) { @@ -17,6 +18,10 @@ export default class InputWidget extends React.Component { onChange = (e) => { var value = e.target.value; + // if this component property is an array, then turn the string into an array + if (this.props.schema.type === 'array') { + value = value.split(','); + } this.setState({ value: value }); if (this.props.onChange) { this.props.onChange(this.props.name, value); diff --git a/src/editor/components/components/Mixins.js b/src/editor/components/widgets/Mixins.js similarity index 95% rename from src/editor/components/components/Mixins.js rename to src/editor/components/widgets/Mixins.js index d3f951542..569c76adf 100644 --- a/src/editor/components/components/Mixins.js +++ b/src/editor/components/widgets/Mixins.js @@ -143,11 +143,12 @@ export default class Mixin extends React.Component { IndicatorSeparator: () => null }} formatGroupLabel={formatGroupLabel} - isMulti={true} + isMulti + isClearable={false} + isSearchable={true} placeholder="Search mixins..." - noResultsText="No mixins found" + noOptionsMessage={() => 'No mixins found'} onChange={this.updateMixins.bind(this)} - simpleValue value={this.state.mixins} /> ) : ( @@ -162,12 +163,11 @@ export default class Mixin extends React.Component { }} formatGroupLabel={formatGroupLabel} isMulti={false} - isSearchable={true} isClearable={false} + isSearchable={true} placeholder="Search models..." - noResultsText="No models found" + noOptionsMessage={() => 'No models found'} onChange={this.updateMixinSingle.bind(this)} - simpleValue value={this.state.mixins} /> )} diff --git a/src/editor/components/widgets/ModelsArrayWidget.js b/src/editor/components/widgets/ModelsArrayWidget.js new file mode 100644 index 000000000..17b1ea983 --- /dev/null +++ b/src/editor/components/widgets/ModelsArrayWidget.js @@ -0,0 +1,156 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import Select, { components } from 'react-select'; +import Events from '../../lib/Events'; +import { DropdownArrowIcon } from '../../icons'; + +export default class ModelsArrayWidget extends React.Component { + static propTypes = { + entity: PropTypes.object.isRequired, + componentname: PropTypes.string.isRequired + }; + + constructor(props) { + super(props); + this.state = { modelsArrayWidget: this.getModelsArrayValueForWidget() }; + } + + onEntityUpdate = (detail) => { + if (detail.entity !== this.props.entity) { + return; + } + if (detail.component === this.props.componentname) { + this.setState({ modelsArrayWidget: this.getModelsArrayValueForWidget() }); + } + }; + + componentDidMount() { + Events.on('entityupdate', this.onEntityUpdate); + } + + componentWillUnmount() { + Events.off('entityupdate', this.onEntityUpdate); + } + + componentDidUpdate(prevProps, prevState) { + if (this.props.entity === prevProps.entity) { + return; + } + this.setState({ modelsArrayWidget: this.getModelsArrayValueForWidget() }); + } + + getModelsArrayValueForWidget() { + console.log(this.props.entity.getAttribute(this.props.componentname)); + const modelsArrayRaw = this.props.entity.getAttribute( + this.props.componentname + )?.modelsArray; + const modelsArrayTransformed = modelsArrayRaw.map((v) => ({ + label: v, + value: v + })); + return modelsArrayTransformed; + } + + getGroupedMixinOptions = () => { + const mixinElements = document.querySelectorAll('a-mixin'); + const groupedArray = []; + let categoryName, mixinId; + + const groupedObject = {}; + for (let mixinEl of Array.from(mixinElements)) { + categoryName = mixinEl.getAttribute('category'); + if (!categoryName) continue; + mixinId = mixinEl.id; + if (!groupedObject[categoryName]) { + groupedObject[categoryName] = []; + } + groupedObject[categoryName].push({ label: mixinId, value: mixinId }); + } + + for (let categoryName of Object.keys(groupedObject)) { + groupedArray.push({ + label: categoryName, + options: groupedObject[categoryName] + }); + } + return groupedArray; + }; + + updateModels = (value) => { + const entity = this.props.entity; + this.setState({ modelsArrayWidget: value }); + const modelsArrayForSaving = value.map((v) => v.value); + const entityUpdateCommand = { + entity: entity, + component: this.props.componentname, // such as 'street-generated-clones__1' + property: 'modelsArray', + value: modelsArrayForSaving + }; + AFRAME.INSPECTOR.execute('entityupdate', entityUpdateCommand); + }; + + render() { + const groupStyles = { + display: 'flex', + alignItems: 'center', + justifyContent: 'space-between' + }; + + const formatGroupLabel = (data) => ( +
+ {data.label} +
+ ); + + const handleHeaderClick = (id) => { + const node = document.querySelector(`#${id}`).parentElement + .nextElementSibling; + const classes = node.classList; + if (classes.contains('collapsed')) { + node.classList.remove('collapsed'); + } else { + node.classList.add('collapsed'); + } + }; + + const CustomGroupHeading = (props) => { + return ( +
handleHeaderClick(props.id)} + > + +
+ ); + }; + + return ( +
+
+ Models + +