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} /> -