diff --git a/src/editor/components/Main.js b/src/editor/components/Main.js
index effe5563d..273b8a206 100644
--- a/src/editor/components/Main.js
+++ b/src/editor/components/Main.js
@@ -24,8 +24,6 @@ import useStore from '@/store';
import { AIChatProvider } from '../contexts/AIChatContext';
import AIChatPanel from './scenegraph/AIChatPanel';
-THREE.ImageUtils.crossOrigin = '';
-
// Define the libraries array as a constant outside of the component
const GOOGLE_MAPS_LIBRARIES = ['places'];
diff --git a/src/editor/components/elements/AddGeneratorComponent.js b/src/editor/components/elements/AddGeneratorComponent.js
index f321600a5..f7d1a1ddf 100644
--- a/src/editor/components/elements/AddGeneratorComponent.js
+++ b/src/editor/components/elements/AddGeneratorComponent.js
@@ -31,7 +31,7 @@ export default class AddGeneratorComponent extends React.Component {
id = id
.trim()
.toLowerCase()
- .replace(/[^a-z0-9]/g, '');
+ .replace(/[^a-z0-9-]/g, '');
// With the transform, id could be empty string, so we need to check again.
}
if (id) {
diff --git a/src/editor/components/elements/PropertyRow.js b/src/editor/components/elements/PropertyRow.js
index 82e7902c1..8a16770c4 100644
--- a/src/editor/components/elements/PropertyRow.js
+++ b/src/editor/components/elements/PropertyRow.js
@@ -44,10 +44,13 @@ export default class PropertyRow extends React.Component {
getWidget() {
const props = this.props;
- const isMap =
- props.componentname === 'material' &&
- (props.name === 'envMap' || props.name === 'src');
let type = props.schema.type;
+
+ if (props.componentname === 'material' && props.name === 'envMap') {
+ // material envMap has the wrong type string, force it to map
+ type = 'map';
+ }
+
if (
(props.componentname === 'animation' ||
props.componentname.startsWith('animation__')) &&
@@ -60,15 +63,17 @@ export default class PropertyRow extends React.Component {
type = 'boolean';
}
- const value =
- props.schema.type === 'selector'
+ let value =
+ type === 'selector'
? props.entity.getDOMAttribute(props.componentname)?.[props.name]
: props.data;
+ if (type === 'string' && value && typeof value !== 'string') {
+ // Allow editing a custom type like event-set component schema
+ value = props.schema.stringify(value);
+ }
+
const widgetProps = {
- componentname: props.componentname,
- entity: props.entity,
- isSingle: props.isSingle,
name: props.name,
onChange: function (name, value) {
// Auto-switch to custom variant for building segments when modifying certain properties
@@ -106,7 +111,8 @@ export default class PropertyRow extends React.Component {
onEntityUpdate: props.onEntityUpdate
});
},
- value: value
+ value: value,
+ id: this.id
};
const numberWidgetProps = {
min: props.schema.hasOwnProperty('min') ? props.schema.min : -Infinity,
@@ -122,7 +128,7 @@ export default class PropertyRow extends React.Component {
/>
);
}
- if (type === 'map' || isMap) {
+ if (type === 'map') {
return ;
}
@@ -151,14 +157,6 @@ export default class PropertyRow extends React.Component {
return ;
}
default: {
- if (
- props.schema.type === 'string' &&
- widgetProps.value &&
- typeof widgetProps.value !== 'string'
- ) {
- // Allow editing a custom type like event-set component schema
- widgetProps.value = props.schema.stringify(widgetProps.value);
- }
return ;
}
}
diff --git a/src/editor/components/modals/ModalTextures.js b/src/editor/components/modals/ModalTextures.js
index 7d978a1a3..773757fe1 100644
--- a/src/editor/components/modals/ModalTextures.js
+++ b/src/editor/components/modals/ModalTextures.js
@@ -2,32 +2,12 @@ import React from 'react';
import PropTypes from 'prop-types';
import Events from '../../lib/Events';
import Modal from '@shared/components/Modal/Modal.jsx';
-import { insertNewAsset } from '../../lib/assetsUtils';
-
-function getFilename(url, converted = false) {
- var filename = url.split('/').pop();
- if (converted) {
- filename = getValidId(filename);
- }
- return filename;
-}
-
-function isValidId(id) {
- // The correct re should include : and . but A-frame seems to fail while accessing them
- var re = /^[A-Za-z]+[\w-]*$/;
- return re.test(id);
-}
-
-function getValidId(name) {
- // info.name.replace(/\.[^/.]+$/, '').replace(/\s+/g, '')
- return name
- .split('.')
- .shift()
- .replace(/\s/, '-')
- .replace(/^\d+\s*/, '')
- .replace(/[\W]/, '')
- .toLowerCase();
-}
+import {
+ getFilename,
+ getIdFromUrl,
+ insertNewAsset,
+ isValidId
+} from '../../lib/assetsUtils';
export default class ModalTextures extends React.Component {
static propTypes = {
@@ -157,15 +137,20 @@ export default class ModalTextures extends React.Component {
var self = this;
function onImageLoaded(img) {
var src = self.preview.current.src;
+ var name = getFilename(src, true);
+ var existingAssetId = getIdFromUrl(src);
+ if (existingAssetId) {
+ name = existingAssetId;
+ }
self.setState({
preview: {
width: self.preview.current.naturalWidth,
height: self.preview.current.naturalHeight,
src: src,
id: '',
- name: getFilename(src, true),
+ name: name,
filename: getFilename(src),
- type: 'new',
+ type: existingAssetId ? 'asset' : 'new',
loaded: true,
value: 'url(' + src + ')'
}
@@ -178,12 +163,6 @@ export default class ModalTextures extends React.Component {
this.imageName.current.focus();
};
- onNameKeyUp = (event) => {
- if (event.keyCode === 13 && this.isValidAsset()) {
- this.addNewAsset();
- }
- };
-
onNameChanged = (event) => {
var state = this.state.preview;
state.name = event.target.value;
@@ -192,6 +171,7 @@ export default class ModalTextures extends React.Component {
toggleNewDialog = () => {
this.setState({ addNewDialogOpened: !this.state.addNewDialogOpened });
+ this.clear();
};
clear() {
@@ -215,23 +195,19 @@ export default class ModalTextures extends React.Component {
this.setState({ newUrl: e.target.value });
};
- isValidAsset() {
- let validUrl = isValidId(this.state.preview.name);
- let validAsset = this.state.preview.loaded && validUrl;
- return validAsset;
- }
-
addNewAsset = () => {
- var self = this;
+ if (this.state.preview.type === 'asset') {
+ return;
+ }
+
insertNewAsset(
'img',
this.state.preview.name,
this.state.preview.src,
- true,
- function () {
- self.generateFromAssets();
- self.setState({ addNewDialogOpened: false });
- self.clear();
+ () => {
+ this.generateFromAssets();
+ this.setState({ addNewDialogOpened: false });
+ this.clear();
}
);
};
@@ -243,15 +219,20 @@ export default class ModalTextures extends React.Component {
renderRegistryImages() {
var self = this;
let selectSample = function (image) {
+ let name = getFilename(image.name, true);
+ const existingAssetId = getIdFromUrl(image.src);
+ if (existingAssetId) {
+ name = existingAssetId;
+ }
self.setState({
preview: {
width: image.width,
height: image.height,
src: image.src,
id: '',
- name: getFilename(image.name, true),
+ name: name,
filename: getFilename(image.src),
- type: 'registry',
+ type: existingAssetId ? 'asset' : 'registry',
loaded: true,
value: 'url(' + image.src + ')'
}
@@ -291,8 +272,14 @@ export default class ModalTextures extends React.Component {
let loadedTextures = this.state.loadedTextures;
let preview = this.state.preview;
- let validUrl = isValidId(this.state.preview.name);
- let validAsset = this.isValidAsset();
+ let validId = isValidId(this.state.preview.name);
+ let assetIdTaken =
+ validId && !!document.getElementById(this.state.preview.name);
+ let validAsset =
+ this.state.preview.loaded &&
+ validId &&
+ !assetIdTaken &&
+ this.state.preview.type !== 'asset';
let addNewAssetButton = this.state.addNewDialogOpened
? 'BACK'
@@ -320,6 +307,7 @@ export default class ModalTextures extends React.Component {
value={this.state.newUrl}
onChange={this.onUrlChange}
onKeyUp={this.onNewUrl}
+ spellCheck="false"
/>
@@ -343,13 +331,33 @@ export default class ModalTextures extends React.Component {
0 && !validUrl ? 'error' : ''
+ this.state.preview.name.length > 0 &&
+ (!validId || assetIdTaken)
+ ? 'error'
+ : ''
}
+ readOnly={preview.type === 'asset'}
type="text"
value={this.state.preview.name}
onChange={this.onNameChanged}
- onKeyUp={this.onNameKeyUp}
+ onKeyUp={(event) => {
+ if (event.keyCode === 13 && validAsset) {
+ this.addNewAsset();
+ }
+ }}
+ spellCheck="false"
/>
+ {preview.type !== 'asset' && assetIdTaken && (
+
+ Name already taken by another asset or entity
+
+ )}
+ {this.state.preview.name.length > 0 && !validId && (
+ Name is not valid
+ )}
+ {preview.type === 'asset' && (
+ Texture already loaded
+ )}
null}
/>
-