Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
<template>
<section class="editor-wrapper">
<LoadingScreenWrapper
v-if="!isDeviceRunning"
:state="computedStatus"
/>

<iframe
v-else
ref="iframe"
width="100%"
height="100%"
name="immersive-editor-iframe"
:src="device.editor.url"
referrerpolicy="strict-origin-when-cross-origin"
allowfullscreen
:style="{'pointer-events': disableEvents ? 'none' : 'auto'}"
data-el="editor-iframe"
/>
</section>
</template>

<script>
import LoadingScreenWrapper from './LoadingScreenWrapper.vue'

export default {
name: 'RemoteInstanceEditorWrapper',
components: { LoadingScreenWrapper },
props: {
device: {
required: false,
type: Object,
default: null
},
disableEvents: {
type: Boolean,
default: false
}
},
computed: {
isDeviceRunning () {
return this.computedStatus === 'running'
},
computedStatus () {
if (!this.device || !Object.prototype.hasOwnProperty.call(this.device, 'editor')) {
return 'loading'
}

return this.device.status
}
}
}
</script>

<style scoped lang="scss">

</style>
127 changes: 127 additions & 0 deletions frontend/src/pages/device/Editor/index.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
<template>
<div ref="resizeTarget" class="ff--immersive-editor-wrapper" :class="{resizing: isEditorResizing}">
<EditorWrapper
:url="device?.editor?.url"
:device="device"
/>

<section
class="tabs-wrapper drawer"
:class="{'open': drawer.open, resizing: isEditorResizing}"
:style="{ width: editorWidthStyle }"
data-el="tabs-drawer"
@mouseenter="handleDrawerMouseEnter"
@mouseleave="handleDrawerMouseLeave"
>
<resize-bar
@mousedown="startEditorResize"
/>
</section>
</div>
</template>

<script>

import semver from 'semver'
import { mapActions } from 'vuex'

import deviceApi from '../../../api/devices.js'
import ResizeBar from '../../../components/ResizeBar.vue'
import EditorWrapper from '../../../components/immersive-editor/RemoteInstanceEditorWrapper.vue'
import { useDrawerHelper } from '../../../composables/DrawerHelper.js'
import { useResizingHelper } from '../../../composables/ResizingHelper.js'
import Alerts from '../../../services/alerts.js'

export default {
name: 'DeviceEditor',
components: {
ResizeBar,
EditorWrapper
},
setup () {
const {
drawer,
toggleDrawer,
notifyDrawerState,
handleDrawerMouseEnter,
handleDrawerMouseLeave,
runInitialTease,
bindDrawer,
cleanup: cleanupDrawer
} = useDrawerHelper()

const {
startResize: startEditorResize,
widthStyle: editorWidthStyle,
bindResizer: bindDrawerResizer,
isResizing: isEditorResizing
} = useResizingHelper()

return {
startEditorResize,
bindDrawerResizer,
editorWidthStyle,
drawer,
isEditorResizing,
toggleDrawer,
notifyDrawerState,
handleDrawerMouseEnter,
handleDrawerMouseLeave,
runInitialTease,
bindDrawer,
cleanupDrawer
}
},
data () {
return {
agentSupportsDeviceAccess: null,
agentSupportsActions: null,
device: null,
openingTunnel: false,
openTunnelTimeout: null
}
},
computed: {

},
watch: {
device (device) {
if (device && Object.prototype.hasOwnProperty.call(device, 'editor')) {
this.setContextDevice(device)
} else {
Alerts.emit('Unable to connect to the Remote Instance', 'warning')

setTimeout(() => this.$router.push({ name: 'device-overview' }), 2000)
}
}
},
mounted () {
this.loadDevice().catch(err => err)
},
methods: {
...mapActions('context', { setContextDevice: 'setDevice' }),
loadDevice: async function () {
try {
this.device = await deviceApi.getDevice(this.$route.params.id)
} catch (err) {
if (err.status === 403) {
clearTimeout(this.openTunnelTimeout)
return this.$router.push({ name: 'Home' })
}
} finally {
this.loading = false
}

this.agentSupportsDeviceAccess = this.device.agentVersion && semver.gte(this.device.agentVersion, '0.8.0')
this.agentSupportsActions = this.device.agentVersion && semver.gte(this.device.agentVersion, '2.3.0')

// todo we first need to get the device and set the team afterwards
await this.$store.dispatch('account/setTeam', this.device.team.slug)
}
}
}
</script>

<style scoped lang="scss">

</style>
13 changes: 13 additions & 0 deletions frontend/src/pages/device/Editor/routes.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import DeviceEditor from './index.vue'

export default [
{
path: '/device/:id/editor',
name: 'device-editor',
component: DeviceEditor,
meta: {
title: 'Device - Editor',
layout: 'plain'
}
}
]
6 changes: 4 additions & 2 deletions frontend/src/routes.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,10 @@ import PageNotFound from './pages/PageNotFound.vue'

import AccountRoutes from './pages/account/routes.js'
import AdminRoutes from './pages/admin/routes.js'
import DeviceEditorRoutes from './pages/device/Editor/routes.js'
import DeviceRoutes from './pages/device/routes.js'
import HelpRoutes from './pages/help/routes.js'
import EditorRoutes from './pages/instance/Editor/routes.js'
import InstanceEditorRoutes from './pages/instance/Editor/routes.js'
import InstanceRoutes from './pages/instance/routes.js'
import TeamRoutes from './pages/team/routes.js'

Expand All @@ -30,7 +31,8 @@ const routes = [
...TeamRoutes,
...AdminRoutes,
...HelpRoutes,
...EditorRoutes,
...InstanceEditorRoutes,
...DeviceEditorRoutes,
{
name: 'page-not-found',
path: '/:pathMatch(.*)*',
Expand Down
5 changes: 4 additions & 1 deletion frontend/src/store/modules/context/index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
const initialState = () => ({
route: null,
instance: null
instance: null,
device: null
})

const meta = {
Expand Down Expand Up @@ -76,6 +77,7 @@ const mutations = {
state.route = route
},
SET_INSTANCE (state, instance) { state.instance = instance },
SET_DEVICE (state, device) { state.device = device },
CLEAR_INSTANCE (state) { state.instance = null }
}

Expand All @@ -84,6 +86,7 @@ const actions = {
commit('UPDATE_ROUTE', route)
},
setInstance ({ commit }, instance) { commit('SET_INSTANCE', instance) },
setDevice ({ commit }, device) { commit('SET_DEVICE', device) },
clearInstance ({ commit }) { commit('CLEAR_INSTANCE') }
}

Expand Down
21 changes: 20 additions & 1 deletion frontend/src/store/modules/product/assistant/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -85,8 +85,26 @@ const getters = {
immersiveInstance: (state, getters, rootState) => {
return rootState.context.instance
},
immersiveDevice: (state, getters, rootState) => {
return rootState.context.device
},
hasUserSelection: (state) => {
return state.selectedNodes.length
},
allowedInboundOrigins: (state, getters) => {
const allowedOrigins = [window.origin]

if (getters.immersiveInstance?.url) {
allowedOrigins.push(getters.immersiveInstance.url)
}

if (getters.immersiveDevice?.editor?.url) {
// todo this might not be needed because it's just the path to the editor tunnel, not an actual origin
// and the only origin we might receive messages is the current window origin
allowedOrigins.push(getters.immersiveDevice.editor.url)
}

return allowedOrigins
}
}

Expand Down Expand Up @@ -131,10 +149,11 @@ const mutations = {

const actions = {
async handleMessage ({ commit, getters, dispatch }, payload) {
if (payload.origin !== getters.immersiveInstance.url) {
if (!getters.allowedInboundOrigins.includes(payload.origin)) {
console.warn('Received message from unknown origin. Ignoring.')
return
}

switch (true) {
case payload.data.type === 'assistant-ready':
commit('SET_VERSION', payload.data.version)
Expand Down
Loading