Skip to content

Commit

Permalink
feat: initial support for web editor (#2764)
Browse files Browse the repository at this point in the history
* feat: added InspectorPage, loaded and connected scene

* chore: linted

* feat: convert scene into composite

* feat: added mappings

* feat: integrate inspector

* chore: install dependencies

* chore: fix tests

* feat: self host inspector

* chore: added feature flag

* fix: remove hardcoded parent origin

* feat: support sdk7 in schema

* chore: fix tests

* fix: remove localhost from config

* chore: remove ignore
  • Loading branch information
cazala authored Jul 25, 2023
1 parent 9f85869 commit 6582cb6
Show file tree
Hide file tree
Showing 51 changed files with 3,880 additions and 785 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,5 @@ yarn-error.log*

src/ecsScene/scene.js
/public/editor.js
/public/inspector*
/src/ecsScene/ecs.js.raw
3,363 changes: 2,945 additions & 418 deletions package-lock.json

Large diffs are not rendered by default.

5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,15 @@
"@dcl/crypto": "^3.0.1",
"@dcl/hashing": "^1.1.0",
"@dcl/schemas": "^8.2.2",
"@dcl/sdk": "^7.3.2-5603025667.commit-44de38f",
"@dcl/ui-env": "^1.2.0",
"@testing-library/jest-dom": "^5.16.4",
"@testing-library/react": "^11.0.0",
"@testing-library/user-event": "^13.5.0",
"@typechain/ethers-v5": "^10.1.0",
"@types/jszip": "^3.1.6",
"@well-known-components/fetch-component": "^2.0.1",
"ajv": "^8.12.0",
"apollo-boost": "^0.4.8",
"apollo-cache-inmemory": "^1.6.6",
"apollo-client": "^2.6.9",
Expand Down Expand Up @@ -124,7 +126,8 @@
"^!?raw-loader!.*$": "identity-obj-proxy",
"^three/.*$": "identity-obj-proxy",
"^@babylonjs.*$": "identity-obj-proxy",
"^.*/modules/curations/collectionCuration/toasts.*$": "identity-obj-proxy"
"^.*/modules/curations/collectionCuration/toasts.*$": "identity-obj-proxy",
"@dcl/ecs": "identity-obj-proxy"
}
},
"engines": {
Expand Down
18 changes: 18 additions & 0 deletions scripts/postinstall.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,21 @@ const pretty = prettier.format(ecs, {
})
console.log(`Saving file to ${targetEcsPath}`)
fs.writeFileSync(targetEcsPath, pretty, { encoding: 'utf8' })

// Copy inspector assets to the public folder
const inspectorAssetsPath = path.resolve(__dirname, '../node_modules/@dcl/inspector/public')
console.log('Inspector assets:', inspectorAssetsPath)
const files = fs.readdirSync(inspectorAssetsPath)
const publicPath = path.resolve(__dirname, '../public')
console.log('Public folder:', publicPath)
for (const file of files) {
const source = path.resolve(inspectorAssetsPath, file)
const target = path.resolve(publicPath, `inspector-${file}`)
console.log(`> Copying ${file} as inspector-${file}...`)
fs.copyFileSync(source, target)
}
console.log(`> Add "inspector-" prefix to files in inspector-index.html`)
const htmlPath = path.resolve(publicPath, `inspector-index.html`)
const originalHtml = fs.readFileSync(htmlPath, 'utf-8')
const modifiedHtml = originalHtml.replace(/bundle\./g, 'inspector-bundle.')
fs.writeFileSync(htmlPath, modifiedHtml, 'utf-8')
21 changes: 21 additions & 0 deletions src/components/InspectorPage/InspectorPage.container.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { connect } from 'react-redux'
import { RootState } from 'modules/common/types'
import { isLoggedIn } from 'modules/identity/selectors'
import { getCurrentScene } from 'modules/scene/selectors'
import { connectInspector, openInspector } from 'modules/inspector/actions'
import { MapStateProps, MapDispatch, MapDispatchProps } from './InspectorPage.types'
import EditorPage from './InspectorPage'

const mapState = (state: RootState): MapStateProps => {
return {
isLoggedIn: isLoggedIn(state),
scene: getCurrentScene(state)
}
}

const mapDispatch = (dispatch: MapDispatch): MapDispatchProps => ({
onOpen: () => dispatch(openInspector()),
onConnect: iframeId => dispatch(connectInspector(iframeId))
})

export default connect(mapState, mapDispatch)(EditorPage)
10 changes: 10 additions & 0 deletions src/components/InspectorPage/InspectorPage.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
.InspectorPage {
width: 100%;
height: 100%;
}

.InspectorPage iframe {
width: 100%;
height: 100%;
border: none;
}
48 changes: 48 additions & 0 deletions src/components/InspectorPage/InspectorPage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/* eslint-disable no-debugger */
import * as React from 'react'
import { Center, Loader } from 'decentraland-ui'

import { Props, State } from './InspectorPage.types'
import './InspectorPage.css'

export default class InspectorPage extends React.PureComponent<Props, State> {
state: State = {
isLoaded: false
}

componentDidMount() {
const { onOpen } = this.props
onOpen()
}

refIframe = (iframe: HTMLIFrameElement | null) => {
const { onConnect } = this.props
if (iframe) {
iframe.onload = () => {
this.setState({ isLoaded: true })
}
onConnect(iframe.id)
}
}

render() {
const { scene, isLoggedIn } = this.props

if (!isLoggedIn) {
return (
<div className="InspectorPager">
<Center>Sign In</Center>
</div>
)
}

return (
<div className="InspectorPage">
{!this.state.isLoaded && <Loader active />}
{scene && (
<iframe ref={this.refIframe} title="inspector" id="inspector" src={`/inspector-index.html?parent=${window.location.origin}`} />
)}
</div>
)
}
}
18 changes: 18 additions & 0 deletions src/components/InspectorPage/InspectorPage.types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { Dispatch } from 'redux'
import { Scene } from 'modules/scene/types'
import { connectInspector, ConnectInspectorAction, openInspector, OpenInspectorAction } from 'modules/inspector/actions'

export type Props = {
scene: Scene | null
isLoggedIn: boolean
onOpen: typeof openInspector
onConnect: typeof connectInspector
}

export type State = {
isLoaded: boolean
}

export type MapStateProps = Pick<Props, 'isLoggedIn' | 'scene'>
export type MapDispatchProps = Pick<Props, 'onOpen' | 'onConnect'>
export type MapDispatch = Dispatch<OpenInspectorAction | ConnectInspectorAction>
3 changes: 3 additions & 0 deletions src/components/InspectorPage/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import InspectorPage from './InspectorPage.container'

export default InspectorPage
9 changes: 7 additions & 2 deletions src/components/Modals/ImportModal/ImportModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -126,8 +126,13 @@ export default class ImportModal extends React.PureComponent<Props, State> {
}

importedFile.manifest!.project.id = uuidv4()
importedFile.manifest!.scene.id = uuidv4()
importedFile.manifest!.project.sceneId = parsed.scene.id
const sceneId = uuidv4()
if (importedFile.manifest!.scene.sdk6) {
importedFile.manifest!.scene.sdk6.id = sceneId
} else {
importedFile.manifest!.scene.sdk7.id = sceneId
}
importedFile.manifest!.project.sceneId = sceneId

this.analytics.track('Import project', { name: file.name })

Expand Down
2 changes: 1 addition & 1 deletion src/components/ProjectCard/ProjectCard.container.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ const mapState = (state: RootState, ownProps: OwnProps): MapStateProps => {
const { project } = ownProps
const parcels = project.layout.cols * project.layout.rows
const scene = getScenes(state)[project.sceneId]
const items = scene ? Object.keys(scene.entities).length - parcels : 0
const items = scene && scene.sdk6 ? Object.keys(scene.sdk6.entities).length - parcels : 0
const type = getPoolProjects(state)[project.id] ? PreviewType.POOL : PreviewType.PROJECT

return {
Expand Down
4 changes: 3 additions & 1 deletion src/components/SceneDetailPage/SceneDetailPage.container.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { FETCH_ENS_LIST_REQUEST } from 'modules/ens/actions'
import { getLoading as getLoadingENS } from 'modules/ens/selectors'
import { MapStateProps, MapDispatchProps, MapDispatch } from './SceneDetailPage.types'
import SceneDetailPage from './SceneDetailPage'
import { getIsInspectorEnabled } from 'modules/features/selectors'

const mapState = (state: RootState): MapStateProps => {
const projectId = getProjectId(state)
Expand All @@ -29,7 +30,8 @@ const mapState = (state: RootState): MapStateProps => {
isLoadingType(getLoadingENS(state), FETCH_ENS_LIST_REQUEST) ||
isLoadingType(getLoadingDeployment(state), FETCH_DEPLOYMENTS_REQUEST) ||
isLoadingType(getLoadingDeployment(state), FETCH_WORLD_DEPLOYMENTS_REQUEST) ||
isLoadingType(getLoadingLands(state), FETCH_LANDS_REQUEST)
isLoadingType(getLoadingLands(state), FETCH_LANDS_REQUEST),
isInspectorEnabled: getIsInspectorEnabled(state)
}
}

Expand Down
5 changes: 4 additions & 1 deletion src/components/SceneDetailPage/SceneDetailPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ const SceneDetailPage: React.FC<Props> = props => {
}

const renderPage = (project: Project, deployments: Deployment[]) => {
const { isLoadingDeployments, onNavigate, onOpenModal } = props
const { isLoadingDeployments, onNavigate, onOpenModal, isInspectorEnabled } = props
return (
<>
<Section size="large">
Expand Down Expand Up @@ -95,6 +95,9 @@ const SceneDetailPage: React.FC<Props> = props => {
direction="left"
>
<Dropdown.Menu>
{isInspectorEnabled ? (
<Dropdown.Item text="Open Inspector" onClick={() => onNavigate(locations.inspector(project.id))} />
) : null}
<Dropdown.Item text={t('scene_detail_page.actions.duplicate')} onClick={handleDuplicateClick} />
<Dropdown.Item text={t('scene_detail_page.actions.delete')} onClick={handleDeleteClick} />
</Dropdown.Menu>
Expand Down
4 changes: 2 additions & 2 deletions src/components/SceneDetailPage/SceneDetailPage.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,18 @@ import { Project } from 'modules/project/types'
import { Deployment } from 'modules/deployment/types'
import { openModal, OpenModalAction } from 'modules/modal/actions'
import { deleteProject, duplicateProjectRequest, DeleteProjectAction, DuplicateProjectRequestAction } from 'modules/project/actions'

export type Props = {
project: Project | null
deployments: Deployment[]
isLoading: boolean
isLoadingDeployments: boolean
isInspectorEnabled: boolean
onNavigate: (path: string) => void
onOpenModal: typeof openModal
onDelete: typeof deleteProject
onDuplicate: typeof duplicateProjectRequest
}

export type MapStateProps = Pick<Props, 'project' | 'deployments' | 'isLoading' | 'isLoadingDeployments'>
export type MapStateProps = Pick<Props, 'project' | 'deployments' | 'isLoading' | 'isLoadingDeployments' | 'isInspectorEnabled'>
export type MapDispatchProps = Pick<Props, 'onNavigate' | 'onDelete' | 'onDuplicate' | 'onOpenModal'>
export type MapDispatch = Dispatch<CallHistoryMethodAction | DeleteProjectAction | DuplicateProjectRequestAction | OpenModalAction>
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ const mapState = (state: RootState, ownProps: OwnProps): MapStateProps => {
const { pool } = ownProps
const scene = getScenes(state)[pool.sceneId]
return {
items: scene ? scene.metrics.entities : 0,
items: scene && scene.sdk6 ? scene.sdk6.metrics.entities : 0,
deploymentStatus: getDeploymentStatusByProjectId(state)[ownProps.pool.id]
}
}
Expand Down
4 changes: 2 additions & 2 deletions src/components/SceneViewPage/SceneViewPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ export default class SceneViewPage extends React.PureComponent<Props> {

getObjectCount() {
const { currentScene } = this.props
if (!currentScene) {
if (!currentScene || !currentScene.sdk6) {
return 0
}

Expand All @@ -89,7 +89,7 @@ export default class SceneViewPage extends React.PureComponent<Props> {
return 0
}

const entitiesCount = Object.keys(currentScene.entities).length
const entitiesCount = Object.keys(currentScene.sdk6.entities).length
if (entitiesCount < parcelCount) {
return 0
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { Scene } from 'modules/scene/types'
import { SceneSDK6 } from 'modules/scene/types'
import { Asset } from 'modules/asset/types'

export type Props = {
id: string
label?: string
value: string
entities: Scene['entities']
entities: SceneSDK6['entities']
assetsByEntityName: Record<string, Asset>
direction?: 'left' | 'right' | null
filter?: string[]
Expand Down
4 changes: 2 additions & 2 deletions src/ecsScene/scene.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import * as ECS from 'decentraland-ecs'
import { createChannel } from 'decentraland-builder-scripts/channel'
import { createInventory } from 'decentraland-builder-scripts/inventory'
import { DecentralandInterface } from 'decentraland-ecs/dist/decentraland/Types'
import { EntityDefinition, AnyComponent, ComponentData, ComponentType, Scene } from 'modules/scene/types'
import { EntityDefinition, AnyComponent, ComponentData, ComponentType, SceneSDK6 } from 'modules/scene/types'
import { AssetParameterValues } from 'modules/asset/types'

const { Gizmos, SmartItem } = require('decentraland-ecs') as any
Expand Down Expand Up @@ -188,7 +188,7 @@ async function handleExternalAction(message: { type: string; payload: Record<str
}
}

function createComponent(component: AnyComponent, scene: Scene) {
function createComponent(component: AnyComponent, scene: SceneSDK6) {
const { id, type, data } = component

if (!getComponentById(id)) {
Expand Down
4 changes: 3 additions & 1 deletion src/modules/common/reducer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import { syncReducer as sync } from 'modules/sync/reducer'
import { thirdPartyReducer as thirdParty } from 'modules/thirdParty/reducer'
import { tileReducer as tile } from 'modules/tile/reducer'
import { uiReducer as ui } from 'modules/ui/reducer'
import { inspectorReducer as inspector } from 'modules/inspector/reducer'

export function createRootReducer(history: History) {
return storageReducerWrapper(
Expand Down Expand Up @@ -73,7 +74,8 @@ export function createRootReducer(history: History) {
translation,
ui,
wallet,
features
features,
inspector
})
)
}
4 changes: 3 additions & 1 deletion src/modules/common/sagas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ import { entitySaga } from 'modules/entity/sagas'
import { collectionCurationSaga } from 'modules/curations/collectionCuration/sagas'
import { getPeerWithNoGBCollectorURL } from './utils'
import { itemCurationSaga } from 'modules/curations/itemCuration/sagas'
import { inspectorSaga } from 'modules/inspector/sagas'

const profileSaga = createProfileSaga({ peerUrl: PEER_URL, peerWithNoGbCollectorUrl: getPeerWithNoGBCollectorURL() })

Expand Down Expand Up @@ -81,6 +82,7 @@ export function* rootSaga(builderAPI: BuilderAPI, newBuilderClient: BuilderClien
walletSaga(),
collectionCurationSaga(builderAPI),
itemCurationSaga(builderAPI),
featuresSaga({ polling: { apps: [ApplicationName.BUILDER], delay: 60000 /** 60 seconds */ } })
featuresSaga({ polling: { apps: [ApplicationName.BUILDER], delay: 60000 /** 60 seconds */ } }),
inspectorSaga()
])
}
2 changes: 2 additions & 0 deletions src/modules/common/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import { UndoableSceneState } from 'modules/scene/reducer'
import { EntityState } from 'modules/entity/reducer'
import { CollectionCurationState } from 'modules/curations/collectionCuration/reducer'
import { ItemCurationState } from 'modules/curations/itemCuration/reducer'
import { InspectorState } from 'modules/inspector/reducer'

const storageLoad = () => action(STORAGE_LOAD, {} as RootState)
export type StorageLoadAction = ReturnType<typeof storageLoad>
Expand Down Expand Up @@ -75,6 +76,7 @@ export type RootState = {
collectionCuration: CollectionCurationState
itemCuration: ItemCurationState
features: FeaturesState
inspector: InspectorState
}

export type RootStore = Store<RootState>
Expand Down
4 changes: 2 additions & 2 deletions src/modules/deployment/sagas.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ describe('when handling deploy to world request', () => {
return expectSaga(deploymentSaga, builderAPI, catalystClient)
.provide([
[matchers.select(getData), { [projectId]: { id: projectId } }],
[matchers.call.fn(getSceneByProjectId), {}],
[matchers.call.fn(getSceneByProjectId), { sdk6: {} }],
[matchers.call.fn(getIdentity), {}],
[matchers.select(getName), 'author'],
[matchers.select(getMedia), { north: 'north', south: 'south', east: 'east', west: 'west', preview: 'preview' }],
Expand All @@ -83,7 +83,7 @@ describe('when handling deploy to world request', () => {
return expectSaga(deploymentSaga, builderAPI, catalystClient)
.provide([
[matchers.select(getData), { [projectId]: { id: projectId } }],
[matchers.call.fn(getSceneByProjectId), {}],
[matchers.call.fn(getSceneByProjectId), { sdk6: {} }],
[matchers.call.fn(getIdentity), {}],
[matchers.select(getName), 'author'],
[matchers.select(getMedia), { north: 'north', south: 'south', east: 'east', west: 'west', preview: 'preview' }],
Expand Down
Loading

0 comments on commit 6582cb6

Please sign in to comment.