Skip to content
2 changes: 0 additions & 2 deletions src/editor/components/Main.js
Original file line number Diff line number Diff line change
Expand Up @@ -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'];

Expand Down
2 changes: 1 addition & 1 deletion src/editor/components/elements/AddGeneratorComponent.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
34 changes: 16 additions & 18 deletions src/editor/components/elements/PropertyRow.js
Original file line number Diff line number Diff line change
Expand Up @@ -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__')) &&
Expand All @@ -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
Expand Down Expand Up @@ -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,
Expand All @@ -122,7 +128,7 @@ export default class PropertyRow extends React.Component {
/>
);
}
if (type === 'map' || isMap) {
if (type === 'map') {
return <TextureWidget {...widgetProps} />;
}

Expand Down Expand Up @@ -151,14 +157,6 @@ export default class PropertyRow extends React.Component {
return <BooleanWidget {...widgetProps} />;
}
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 <InputWidget {...widgetProps} schema={props.schema} />;
}
}
Expand Down
112 changes: 60 additions & 52 deletions src/editor/components/modals/ModalTextures.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand Down Expand Up @@ -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 + ')'
}
Expand All @@ -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;
Expand All @@ -192,6 +171,7 @@ export default class ModalTextures extends React.Component {

toggleNewDialog = () => {
this.setState({ addNewDialogOpened: !this.state.addNewDialogOpened });
this.clear();
};

clear() {
Expand All @@ -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();
}
);
};
Expand All @@ -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 + ')'
}
Expand Down Expand Up @@ -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'
Expand Down Expand Up @@ -320,6 +307,7 @@ export default class ModalTextures extends React.Component {
value={this.state.newUrl}
onChange={this.onUrlChange}
onKeyUp={this.onNewUrl}
spellCheck="false"
/>
</li>
<li>
Expand All @@ -343,13 +331,33 @@ export default class ModalTextures extends React.Component {
<input
ref={this.imageName}
className={
this.state.preview.name.length > 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 && (
<div className="iderror">
Name already taken by another asset or entity
</div>
)}
{this.state.preview.name.length > 0 && !validId && (
<div className="iderror">Name is not valid</div>
)}
{preview.type === 'asset' && (
<div className="iderror">Texture already loaded</div>
)}
<img
ref={this.preview}
width="155px"
Expand Down
9 changes: 3 additions & 6 deletions src/editor/components/widgets/BooleanWidget.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,7 @@ import classNames from 'classnames';

export default class BooleanWidget extends React.Component {
static propTypes = {
componentname: PropTypes.string.isRequired,
entity: PropTypes.object,
id: PropTypes.string,
name: PropTypes.string.isRequired,
onChange: PropTypes.func,
value: PropTypes.bool
Expand Down Expand Up @@ -36,8 +35,6 @@ export default class BooleanWidget extends React.Component {
};

render() {
const id = this.props.componentname + '.' + this.props.name;

const checkboxClasses = classNames({
checkboxAnim: true,
checked: this.state.value
Expand All @@ -46,13 +43,13 @@ export default class BooleanWidget extends React.Component {
return (
<div className={checkboxClasses} onClick={this.onChange}>
<input
id={id}
id={this.props.id}
type="checkbox"
checked={this.state.value}
value={this.state.value}
onChange={() => null}
/>
<label htmlFor={id} onClick={(e) => e.stopPropagation()} />
<label htmlFor={this.props.id} onClick={(e) => e.stopPropagation()} />
</div>
);
}
Expand Down
4 changes: 2 additions & 2 deletions src/editor/components/widgets/ColorWidget.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@ import PropTypes from 'prop-types';

export default class ColorWidget extends React.Component {
static propTypes = {
componentname: PropTypes.string.isRequired,
entity: PropTypes.object,
id: PropTypes.string,
name: PropTypes.string.isRequired,
onChange: PropTypes.func,
value: PropTypes.string
Expand Down Expand Up @@ -77,6 +76,7 @@ export default class ColorWidget extends React.Component {
onChange={this.onChange}
/>
<input
id={this.props.id}
type="text"
className="color_value"
value={this.state.value}
Expand Down
5 changes: 3 additions & 2 deletions src/editor/components/widgets/InputWidget.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@ import PropTypes from 'prop-types';

export default class InputWidget extends React.Component {
static propTypes = {
componentname: PropTypes.string,
entity: PropTypes.object,
id: PropTypes.string,
name: PropTypes.string.isRequired,
onChange: PropTypes.func,
value: PropTypes.any,
Expand Down Expand Up @@ -37,10 +36,12 @@ export default class InputWidget extends React.Component {
render() {
return (
<input
id={this.props.id}
type="text"
className="string"
value={this.state.value || ''}
onChange={this.onChange}
spellCheck="false"
/>
);
}
Expand Down
6 changes: 3 additions & 3 deletions src/editor/components/widgets/NumberWidget.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,10 @@ import PropTypes from 'prop-types';

export default class NumberWidget extends React.Component {
static propTypes = {
componentname: PropTypes.string,
entity: PropTypes.object,
id: PropTypes.string,
max: PropTypes.number,
min: PropTypes.number,
name: PropTypes.string,
name: PropTypes.string.isRequired,
onChange: PropTypes.func,
precision: PropTypes.number,
step: PropTypes.number,
Expand Down Expand Up @@ -164,6 +163,7 @@ export default class NumberWidget extends React.Component {
<div className="inputBlock">
{helpString}
<input
id={this.props.id}
ref={this.input}
className="number"
type="text"
Expand Down
Loading
Loading