link scenarios to backend

This commit is contained in:
2025-03-15 11:23:55 +02:00
parent 6aad752ce3
commit 2a44578430
13 changed files with 233 additions and 75 deletions
@@ -0,0 +1,51 @@
<template>
<slot name="activator" v-bind="activatorProps" @click="dialog = !dialog"></slot>
<v-dialog transition="dialog-bottom-transition" fullscreen v-model="dialog">
<v-container>
<v-row>
<v-col v-for="(v, i) in items" :key="i" cols="12" xs="6" sm="4" md="3" xl="2" class="position-relative">
<v-img :src="`/asset/thumb/${v.asset?.thumb}`" @click="select(v.id)"></v-img>
<div class="d-flex">
<span class="flex-grow-1">{{ v.name }}</span>
<v-icon color="primary" size="x-large" class="position-absolute top-0 left-0 ma-6">mdi-{{ $p.objectTypes.find(t=>t.value == v.type).icon }}</v-icon>
<!-- <v-btn density="compact" variant="text" icon="mdi-pencil-outline" :to="`/game-objects/${v.id}`" color="primary"></v-btn> -->
</div>
</v-col>
</v-row>
</v-container>
</v-dialog>
</template>
<script>
export default {
props:[
'modelValue', 'type'
],
emits:['select'],
data(){
return {
items: [],
activatorProps:{},
dialog: false
}
},
mounted(){
console.log(this.activatorProps, this.cls)
},
async created(){
this.items = (await this.$api.gameObject.search({
type: { $in: this.$p.objectTypes.filter(t=>t.type == this.type || !this.type).map(t=>t.value) }
})).data.data
},
methods:{
select(id){
this.$emit('select', id);
this.dialog = false;
}
}
}
</script>
+10 -5
View File
@@ -2,12 +2,16 @@
<teleport to=".scene-designer" v-if="active">
<g @mousedown="$emit('target', {target:vd, attrs:['x1', 'y1'], delta: true})" :class="{gameObject: true, selected}">
<line :x1="vd.x1" :y1="vd.y1" :x2="parent.vd.x1" :y2="parent.vd.y1"></line>
<svg-icon :src="`/asset/thumb/${modelValue.id}.webp`" :x="vd.x1" :y="vd.y1" :size="37"></svg-icon>
<svg-icon :src="`/asset/thumb/${modelValue.id||0}.webp`" :x="vd.x1" :y="vd.y1" :size="37"></svg-icon>
</g>
</teleport>
<v-list density="compact" nav v-if="selected">
<v-list-item prepend-icon="mdi-panorama-outline" :title="$l.addScene" value="scene"></v-list-item>
</v-list>
<v-card v-if="selected">
<asset-selector @select="modelValue.id = $event" type="GameObject">
<template v-slot:activator="props">
<v-btn v-bind="props" icon="mdi-panorama-outline"></v-btn>
</template>
</asset-selector>
</v-card>
</template>
<script>
@@ -15,7 +19,7 @@ import SvgIcon from './SvgIcon.vue';
import Utils from '@/lib/utils';
export default {
emits:['target'],
emits:['target'],
components: { SvgIcon },
data(){
return {
@@ -26,6 +30,7 @@ export default {
this.active = true;
},
props:{
//context: Object,
modelValue: Object,
vd: Object,
selected: Boolean,
+11 -3
View File
@@ -1,24 +1,31 @@
<template>
<teleport to=".scene-designer" v-if="active">
<g @mousedown="$emit('target', {target:vd, attrs:['x1', 'y1'], delta: true})" :class="{scene: true, selected}">
<svg-icon :src="`/asset/thumb/${modelValue.environment}.webp`" :x="vd.x1" :y="vd.y1" :size="65"></svg-icon>
<svg-icon :src="`/asset/thumb/${modelValue.environment||0}.webp`" :x="vd.x1" :y="vd.y1" :size="65"></svg-icon>
</g>
</teleport>
<v-card title="Scene" v-if="selected">
<v-form class="pa-4">
<v-text-field density="compact" :label="$l.name" v-model="modelValue.name"></v-text-field>
</v-form>
<v-btn prepend-icon="mdi-panorama-outline" ></v-btn>
<asset-selector @select="modelValue.environment = $event" type="Scene">
<template v-slot:activator="props">
<v-btn v-bind="props" icon="mdi-panorama-outline"></v-btn>
</template>
</asset-selector>
</v-card>
</template>
<script>
import SvgIcon from './SvgIcon.vue';
import Utils from '@/lib/utils';
import AssetSelector from './AssetSelector.vue';
export default {
emits:['target'],
components: { SvgIcon },
components: { SvgIcon, AssetSelector },
data(){
return {
active: false
@@ -28,6 +35,7 @@ export default {
this.active = true;
},
props:{
//context: Object,
modelValue: Object,
vd: Object,
selected: Boolean,
+42 -16
View File
@@ -1,28 +1,33 @@
<template>
<div class="container my-3">
<v-btn-toggle variant="tonal" density="compact" class="mx-auto" v-model="mode" color="blue">
<v-btn size="small" class="text-none" value="default" prepend-icon="mdi-cursor-default-click">Pointer</v-btn>
<v-btn size="small" class="text-none" value="default"
prepend-icon="mdi-cursor-default-click">Pointer</v-btn>
<v-btn size="small" class="text-none" value="select" prepend-icon="mdi-select-multiple">Select</v-btn>
<v-btn size="small" class="text-none" value="move" prepend-icon="mdi-cursor-move">Move</v-btn>
<v-btn size="small" class="text-none" value="pan" prepend-icon="mdi-hand-back-right-outline">Pan</v-btn>
<v-btn size="small" class="text-none" value="scene" prepend-icon="mdi-panorama-outline">{{ $l.addScene }}</v-btn>
<v-btn size="small" class="text-none" value="object" prepend-icon="mdi-bird">{{ $l.addScene }}</v-btn>
<v-btn size="small" class="text-none" value="task" prepend-icon="mdi-checkbox-marked-circle-plus-outline">{{ $l.addScene }}</v-btn>
<v-btn size="small" class="text-none" value="Scene" prepend-icon="mdi-panorama-outline">Add scene</v-btn>
<v-btn size="small" class="text-none" value="GameObject"
v-if="selectedItem.length == 1 && selectedItem[0].__type == 'Scene'" prepend-icon="mdi-bird">Add game
object</v-btn>
<v-btn size="small" class="text-none" value="Task"
v-if="selectedItem.length == 1 && selectedItem[0].__type == 'GameObject'"
prepend-icon="mdi-checkbox-marked-circle-plus-outline">Add task</v-btn>
</v-btn-toggle>
<div @wheel="onWheel" @mousedown="onMouseDown" @mouseup="onMouseUp" @mousemove="onDrag"
:class="`svg-container ${mode}`" ref="svgContainer">
<svg class="scene-designer" @resize="resize" :width="viewBox.w" :height="viewBox.h" :viewBox="`${vb.x} ${vb.y} ${vb.w} ${vb.h}`" x="0" y="0"
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<svg class="scene-designer" @resize="resize" :width="viewBox.w" :height="viewBox.h"
:viewBox="`${vb.x} ${vb.y} ${vb.w} ${vb.h}`" x="0" y="0" xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink">
<SvgRectangle v-model="selector" class="selector"></SvgRectangle>
</svg>
</div>
<v-navigation-drawer location="right">
<template v-for="(item, i) in flatItems" :key="i">
<component :is="components[item.__type]" :ref="'svg-'+item.id"
:vd="item.vd" v-model="item.data" @target="setTarget($event, item)"
:visible="item.visible" :cid="item.id"
:parent="item.__parent" :selected="selectedItem.includes(item)">
<component :is="components[item.__type]" :ref="'svg-'+item.id" :vd="item.vd" v-model="item.data"
@target="setTarget($event, item)" :visible="item.visible" :cid="item.id" :parent="item.__parent"
:selected="selectedItem.includes(item)">
</component>
</template>
</v-navigation-drawer>
@@ -34,17 +39,20 @@ import GameObject from './GameObject.vue';
import Scene from './Scene.vue';
import SvgRectangle from './SvgRectangle.vue';
import Utils from '@/lib/utils';
import AssetSelector from './AssetSelector.vue';
const components = {
Scene, GameObject
}
export default {
components: { AssetSelector },
props:{
modelValue: Object
},
data(){
return {
context: this,
mode: 'default',
selectedItem: [],
viewBox: {
@@ -64,6 +72,10 @@ export default {
modeStep: 0,
mousedown: false,
target: null,
assetSelector: {
active: false,
type: 'Scene'
}
}
},
mounted(){
@@ -101,7 +113,7 @@ export default {
},
flatItems(){
let fi = [];
this.items.forEach(i=>{
this.items?.forEach(i=>{
i.__type = 'Scene';
fi.push(i);
i.data?.gameObjects?.forEach(go=>{
@@ -228,10 +240,18 @@ export default {
let id, nid = 1;
do {
id = `${this.components[this.mode].name}-${nid++}`
}while (this.flatItems.find(i=>i.id == id))
this.items.push({
name: this.mode,
data: this.target.target,
}while (this.flatItems.find(i=>i.id == id));
let targetArray = this.items;
if (this.mode == 'GameObject'){
if (this.selectedItem[0].data && !this.selectedItem[0].data.gameObjects){
this.selectedItem[0].data.gameObjects = [];
}
targetArray = this.selectedItem[0].data.gameObjects;
}
targetArray.push({
//__type: this.mode,
vd: this.target.target,
data: {},
visible: true,
id, title: id
})
@@ -284,6 +304,9 @@ export default {
this.viewBox.w = r.clientWidth;
this.viewBox.h = r.clientHeight;
//this.zoom = Math.min(r.clientWidth / this.viewBox.w, r.clientHeight / this.viewBox.h);
},
assetSelected(e, v){
console.log(e, v)
}
}
}
@@ -311,7 +334,7 @@ export default {
}
}
line, path{
stroke: #19c;
stroke: rgb(213, 226, 231);
stroke-width: calc( 2px * var(--svg-scale) );
}
g.selector {
@@ -326,5 +349,8 @@ export default {
&.pan {
cursor: grab;
}
&.Scene, &.GameObject {
cursor: grabbing;
}
}
</style>
+1 -16
View File
@@ -22,22 +22,7 @@ import SceneDesigner from '@/components/SceneDesigner/SceneDesigner.vue';
export default {
data() {
return {
object: {
scenes: [
{
id: 'test',
vd: { x1: 220, y1: 220 },
data: {
environment: 3, intro: 2,
gameObjects: [
{ vd: { x1: 350, y1:350 }, data:{ id: 7, }},
{ vd: { x1: 200, y1:400 }, data:{ id: 8, }},
{ vd: { x1: 70, y1:350 }, data:{ id: 9, }}
]
}
}
]
},
object: {},
valid: false,
rules: {
required: v => v ? true : this.$l.fieldRequired,
+41 -1
View File
@@ -1,9 +1,49 @@
<template>
<v-container>
<v-row>
<v-col v-for="(v, i) in items" :key="i" cols="12" xs="6" sm="4" md="3" xl="2" class="position-relative">
<router-link :to="`/scenarios/${v.id}`">
<v-img :src="`/asset/thumb/${v.sceneThumb[0]}.webp`"></v-img>
</router-link>
<div class="d-flex">
<span class="flex-grow-1">{{ v.name }}</span>
<v-btn density="compact" variant="text" icon="mdi-pencil-outline" :to="`/scenarios/${v.id}`" color="primary"></v-btn>
<v-btn density="compact" variant="text" icon="mdi-close" @click="confirmTarget = v; confirmDialog = true" color="red"></v-btn>
</div>
</v-col>
</v-row>
</v-container>
<v-dialog v-model="confirmDialog" width="auto">
<v-card :title="`${$l.confirmDeletionOf} ${ confirmTarget.name }?`">
<template v-slot:actions>
<v-btn @click="confirmDialog = false">{{ $l.no }}</v-btn>
<v-btn color="red-darken-4" @click="remove(confirmTarget)">{{ $l.yes }}</v-btn>
</template>
</v-card>
</v-dialog>
</template>
<script>
export default {
data(){
return {
items: [],
confirmDialog: false,
confirmTarget: null
}
},
async created(){
this.items = (await this.$api.scenario.search()).data.data
},
methods:{
async remove(item){
await this.$api.scenario.remove(item.id);
this.confirmDialog = false;
this.items.splice(this.items.indexOf(item), 1);
}
}
}
</script>
+12 -1
View File
@@ -2,7 +2,18 @@ import axios from 'axios';
const $ax = axios.create({
baseURL: '/api/',
//transformRequest: data=>
transformRequest: [
(data, headers)=>{
if (data && !(data instanceof FormData)){
data = JSON.stringify(data, (k, v)=>{
return k.startsWith('__') ? undefined : v;
});
headers['Content-Type'] = 'application/json;charset=utf-8';
}
return data;
}
, ...axios.defaults.transformRequest
]
})
export default {
+8 -4
View File
@@ -7,21 +7,25 @@ export default {
value: 'panorama2d',
icon: 'panorama-variant-outline',
title: l.panorama2d,
render: true
render: true,
type: 'Scene'
}, {
value: 'environment3d',
icon: 'panorama-sphere-outline',
title: l.environment3d,
render: true
render: true,
type: 'Scene'
}, {
value: 'object3d',
icon: 'video-3d',
title: l.object3d,
render: true
render: true,
type: 'GameObject'
}, {
value: 'object2d',
icon: 'file-image-outline',
title: l.object2d
title: l.object2d,
type: 'GameObject'
}, {
value: 'player3d',
icon: 'human-greeting',