working on simManage

This commit is contained in:
STEINNI
2026-06-21 21:09:21 +00:00
parent 06a7868882
commit e2f8766b9f
17 changed files with 484 additions and 51 deletions
+39
View File
@@ -0,0 +1,39 @@
---
description: SQL conventions — no table aliases; columns are always prefixed
globs: "**/*.{js,sql,php}"
alwaysApply: false
---
# SQL rules
All tables use **prefixed column names** (`sim_uuid`, `own_usr_id`, `usr_uuid`, `ekfs_agent_id`, …). There is never column ambiguity across joins, so **do not use table aliases**.
## Do not
- Short table aliases: `s`, `o`, `u`, `ekfs`, etc.
- Qualified columns when an alias was the only reason: `s.sim_uuid`, `o.own_sim_uuid`
## Do
- Join on bare prefixed column names.
- Use full table names (or `${qualified}` template vars) in `FROM` / `JOIN` only — no alias after the table.
```sql
-- BAD
SELECT s.sim_name, BIN_TO_UUID(s.sim_uuid) AS simulationUuid
FROM `p42SIM`.simulations s
INNER JOIN `p42GUI`.simowners o ON o.own_sim_uuid = s.sim_uuid
INNER JOIN `p42GUI`.users u ON o.own_usr_id = u.usr_id
WHERE u.usr_uuid = ? AND s.sim_uuid = UUID_TO_BIN(?)
-- GOOD
SELECT sim_name, BIN_TO_UUID(sim_uuid) AS simulationUuid
FROM `p42SIM`.simulations
INNER JOIN `p42GUI`.simowners ON own_sim_uuid = sim_uuid
INNER JOIN `p42GUI`.users ON own_usr_id = usr_id
WHERE usr_uuid = ? AND sim_uuid = UUID_TO_BIN(?)
```
`AS` on **result columns** (e.g. `AS simulationUuid` for the API) is fine — that is not a table alias.
When adding a query, read an existing join in the same repo first and match its style.
+1 -1
View File
@@ -4,4 +4,4 @@
"y": 100, "y": 100,
"z": 100 "z": 100
} }
} }
+11
View File
@@ -0,0 +1,11 @@
{
"maestro": {
"actionsChannel": "system:requests:maestro",
"lifecycleChannel": "system:maestro:lifecycle:[UID]"
},
"observer": {
"actionsChannel": "system:requests:observer",
"subscribeFrequencyMs": 1000,
"cameraDebounceMs": 600
}
}
+1 -9
View File
@@ -44,20 +44,12 @@
"method": "POST" "method": "POST"
}, },
"get": { "get": {
"uri": "/api/sims/{simId}", "uri": "/api/sims/{simulationUuid}",
"method": "GET" "method": "GET"
}, },
"create": { "create": {
"uri": "/api/sims", "uri": "/api/sims",
"method": "PUT" "method": "PUT"
},
"start": {
"uri": "/api/sims/{simId}/start",
"method": "PUT"
},
"pause": {
"uri": "/api/sims/{simId}/pause",
"method": "PUT"
} }
} }
} }
@@ -1,6 +1,6 @@
[ [
{ {
"chan": "system:gps:agents", "chan": "system:observer:subscribed[UID]:agents",
"events": [ "events": [
{ {
"eventName": "move", "eventName": "move",
+4
View File
@@ -397,6 +397,10 @@ div.window > section button[eicbutton][rounded] {
color: #222; color: #222;
} }
[eicdatagrid] .dataset .row:hover {
background-color: #483;
}
/* Customizations to buildoz*/ /* Customizations to buildoz*/
bz-select > button{ bz-select > button{
background: linear-gradient( to bottom, #251, #372 15%, #483 50%, #372 85%, #251 ) !important; background: linear-gradient( to bottom, #251, #372 15%, #483 50%, #372 85%, #251 ) !important;
+1
View File
@@ -14,6 +14,7 @@
], ],
"json": [ "json": [
{"name":"global/app-menu-map.json"}, {"name":"global/app-menu-map.json"},
{"id":"busChannels", "name":"global/busChannels.json"},
{"path": "/app/controllers/common/", "name": "errorController.json", "comment": "Trick to preload errorController stuff, to still have error messages if S3 is down."} {"path": "/app/controllers/common/", "name": "errorController.json", "comment": "Trick to preload errorController stuff, to still have error messages if S3 is down."}
] ]
} }
@@ -3,6 +3,7 @@ class DashboardsController extends WindozController {
constructor(params) { constructor(params) {
super(params) super(params)
this.arenaConfig = app.Assets.Store.json.arenaConfig this.arenaConfig = app.Assets.Store.json.arenaConfig
this.busChannels = app.Assets.Store.json.busChannels
this.eventsMapping = app.Assets.Store.json.eventsMapping this.eventsMapping = app.Assets.Store.json.eventsMapping
console.log('=============>DashboardsController constructor') console.log('=============>DashboardsController constructor')
} }
@@ -21,6 +22,7 @@ class DashboardsController extends WindozController {
const ttb = new app.LoadedModules.Threetobus({ const ttb = new app.LoadedModules.Threetobus({
eventsMapping: this.eventsMapping, eventsMapping: this.eventsMapping,
sceneSize: this.arenaConfig.arenaSize, sceneSize: this.arenaConfig.arenaSize,
observer: this.busChannels.observer,
}) })
ttb.initScene({ ttb.initScene({
axes: true, axes: true,
+1 -1
View File
@@ -39,7 +39,7 @@ class SimsController extends WindozController {
static: true, static: true,
expanded: false, expanded: false,
withSettings: false, withSettings: false,
windowStyle: WindozDomContent.boxFromPrefs('sims.managesimview', { x: 50, y: 50, w: 800, h: 600 }), windowStyle: WindozDomContent.boxFromPrefs('sims.managesimview', { x: 50, y: 50, w: 1000, h: 600 }),
}, },
{ {
models: models, models: models,
+57 -18
View File
@@ -1,20 +1,36 @@
class SimsModel extends WindozModel { class SimsModel extends WindozBusModel {
constructor() { constructor() {
super() super()
this.ressource = '/sims' this.ressource = '/sims'
this.busChannels = app.Assets.Store.json.busChannels
} }
async list() { async list() {
let endpoint = {...app.config.api[this.ressource].list} const endpoint = {...app.config.api[this.ressource].list}
return( const [listResponse, statusList] = await Promise.all([
this.request(endpoint.uri, endpoint.method) this.request(endpoint.uri, endpoint.method),
this.getSimulationsStatus(),
])
const sims = listResponse?.payload ?? []
const statuses = Array.isArray(statusList) ? statusList : (statusList?.simulations ?? [])
const stateBySimulationId = new Map(
statuses.map(item => [item.simulationId, item.state])
) )
return({
...listResponse,
payload: sims.map(sim => ({
...sim,
state: stateBySimulationId.get(sim.simulationUuid) ?? 'idle',
})),
})
} }
async get(simId) { async get(simulationUuid) {
let endpoint = {...app.config.api[this.ressource].get} let endpoint = {...app.config.api[this.ressource].get}
endpoint.uri = endpoint.uri.replace('{simId}', simId) endpoint.uri = endpoint.uri.replace('{simulationUuid}', simulationUuid)
return( return(
this.request(endpoint.uri, endpoint.method) this.request(endpoint.uri, endpoint.method)
) )
@@ -27,20 +43,43 @@ class SimsModel extends WindozModel {
) )
} }
async start(simId) { async startSimulation(simulationUuid) {
let endpoint = {...app.config.api[this.ressource].start} return(this.busActionRequest(
endpoint.uri = endpoint.uri.replace('{simId}', simId) this.busChannels.maestro.actionsChannel,
return( 'STARTSIMULATION',
this.request(endpoint.uri, endpoint.method) {
) simulationUuid,
infraId: null,
},
60000
))
} }
async pause(simId) { async stopSimulation(simulationUuid) {
let endpoint = {...app.config.api[this.ressource].pause} return(this.busActionRequest(
endpoint.uri = endpoint.uri.replace('{simId}', simId) this.busChannels.maestro.actionsChannel,
return( 'STOPSIMULATION',
this.request(endpoint.uri, endpoint.method) { simulationUuid },
) 30000
))
}
async pauseSimulation(simulationUuid) {
return(this.busActionRequest(
this.busChannels.maestro.actionsChannel,
'PAUSESIMULATION',
{ simulationUuid },
30000
))
}
async getSimulationsStatus() {
return(this.busActionRequest(
this.busChannels.maestro.actionsChannel,
'GETSIMULATIONSSTATUS',
{},
15000
))
} }
} }
+134 -8
View File
@@ -9,6 +9,16 @@ export class Threetobus{
this._curEventsMapping = [] this._curEventsMapping = []
this._stagedEventsMapping = options.eventsMapping this._stagedEventsMapping = options.eventsMapping
this.sceneSize = options.sceneSize this.sceneSize = options.sceneSize
this._observerConfig = {
actionsChannel: 'system:requests:observer',
subscribeFrequencyMs: 1000,
cameraDebounceMs: 600,
...options.observer,
}
this._frustumRenderEngine = null
this._frustumDebounceTimer = null
this._frustumCameraChangeHandler = null
this._frustumWatching = false
this.commitConfig() this.commitConfig()
this.cameras = {} this.cameras = {}
@@ -19,8 +29,24 @@ export class Threetobus{
} }
busReconnect(){ busReconnect(){
this.commitConfig() // To resubscribe... this.commitConfig()
//TODO : Not ideal because if we're in the middle of non-commited changes... if(this._frustumWatching) this._scheduleFrustumResubscribe()
}
resolveSubscriberChan(chanTemplate) {
if(typeof(chanTemplate) !== 'string') return(null)
const uid = app.User?.identity?.uuid
if(!uid) return(null)
return(chanTemplate.replace(/\[UID\]/g, uid).replace(/\{uid\}/g, uid))
}
_resolvedEventsMapping() {
if(!Array.isArray(this._stagedEventsMapping)) return([])
return(this._stagedEventsMapping.map(chanObj => {
const chan = this.resolveSubscriberChan(chanObj.chan)
if(!chan) return(null)
return({ ...chanObj, chan })
}).filter(Boolean))
} }
get EventsMapping() { return this._stagedEventsMapping } get EventsMapping() { return this._stagedEventsMapping }
@@ -28,15 +54,21 @@ export class Threetobus{
set EventsMapping(newConfig) { this._stagedEventsMapping = newConfig } set EventsMapping(newConfig) { this._stagedEventsMapping = newConfig }
async commitConfig(){ async commitConfig(){
const resolvedMapping = this._resolvedEventsMapping()
const chansToAdd = [] const chansToAdd = []
const chansToKeep = [] const chansToKeep = []
for(const chanObj of this._stagedEventsMapping){ for(const chanObj of resolvedMapping){
if(this._curEventsMapping.map(item => item.chan).includes(chanObj.chan)) chansToKeep.push(chanObj) if(this._curEventsMapping.map(item => item.chan).includes(chanObj.chan)) chansToKeep.push(chanObj)
else chansToAdd.push(chanObj) else chansToAdd.push(chanObj)
} }
const chansToDel = this._curEventsMapping.filter(item => (!chansToKeep.map(c=>c.chan).includes(item.chan) && !chansToAdd.map(c=>c.chan).includes(item.chan))) const chansToDel = this._curEventsMapping.filter(item => (
await app.MessageBus.subscribe(chansToAdd.map(item => item.chan)) !chansToKeep.map(c => c.chan).includes(item.chan)
await app.MessageBus.unSubscribe(chansToDel.map(item => item.chan)) && !chansToAdd.map(c => c.chan).includes(item.chan)
))
if(app.MessageBus?.connected) {
if(chansToAdd.length) await app.MessageBus.subscribe(chansToAdd.map(item => item.chan))
if(chansToDel.length) await app.MessageBus.unSubscribe(chansToDel.map(item => item.chan))
}
// console.log('subscribe:', chansToAdd.map(item => item.chan)) // console.log('subscribe:', chansToAdd.map(item => item.chan))
// console.log('unSubscribe:', chansToDel) // console.log('unSubscribe:', chansToDel)
@@ -59,7 +91,7 @@ export class Threetobus{
app.MessageBus.removeBusListener(eventToDel.eventName, this.processBusEvent.bind(this, eventToDel.eventName), 'threetobus') app.MessageBus.removeBusListener(eventToDel.eventName, this.processBusEvent.bind(this, eventToDel.eventName), 'threetobus')
} }
this._curEventsMapping = this.deepClone(this._stagedEventsMapping) this._curEventsMapping = this.deepClone(resolvedMapping)
} }
deepClone(obj) { // Needed because structuredClone doesn't take functions (and we have transformers) deepClone(obj) { // Needed because structuredClone doesn't take functions (and we have transformers)
@@ -88,7 +120,8 @@ export class Threetobus{
// if yes : how to discriminate static value from event-mapping definition ? // if yes : how to discriminate static value from event-mapping definition ?
if(mapping.child) id += '_'+mapping.child if(mapping.child) id += '_'+mapping.child
if(id){ if(id){
const obj3D = this.scene.getObjectByName(id) let obj3D = this.scene.getObjectByName(id)
if(!obj3D) obj3D = this._ensurePlaceholderAgent(id)
if(obj3D){ if(obj3D){
this.assignFromConfig(payload, mapping, obj3D) this.assignFromConfig(payload, mapping, obj3D)
} }
@@ -161,6 +194,99 @@ export class Threetobus{
return(path.split('.').reduce((acc, key) => acc?.[key], obj)) return(path.split('.').reduce((acc, key) => acc?.[key], obj))
} }
watchCameraFrustum(renderEngine) {
if(!renderEngine || renderEngine.mode !== '3D') return
if(!app.MessageBus?.config?.enabled) {
console.warn('[Threetobus] MessageBus disabled — camera frustum watch skipped')
return
}
this._frustumRenderEngine = renderEngine
this._frustumWatching = true
if(!this._frustumCameraChangeHandler) {
this._frustumCameraChangeHandler = () => this._scheduleFrustumResubscribe()
renderEngine.controls.addEventListener('change', this._frustumCameraChangeHandler)
}
app.MessageBus.whenConnected(() => {
this.commitConfig()
this._scheduleFrustumResubscribe()
})
}
stopWatchingCameraFrustum() {
this._frustumWatching = false
if(this._frustumDebounceTimer) {
clearTimeout(this._frustumDebounceTimer)
this._frustumDebounceTimer = null
}
if(this._frustumRenderEngine?.controls && this._frustumCameraChangeHandler) {
this._frustumRenderEngine.controls.removeEventListener('change', this._frustumCameraChangeHandler)
}
this._frustumRenderEngine = null
this._frustumCameraChangeHandler = null
}
_scheduleFrustumResubscribe() {
if(!this._frustumWatching) return
if(this._frustumDebounceTimer) clearTimeout(this._frustumDebounceTimer)
this._frustumDebounceTimer = setTimeout(() => {
this._frustumDebounceTimer = null
this._resubscribeCameraFrustum()
}, this._observerConfig.cameraDebounceMs)
}
async _resubscribeCameraFrustum() {
if(!this._frustumWatching || !this._frustumRenderEngine) return
if(!app.MessageBus?.connected) return
const camera = this._frustumRenderEngine.camera
if(!camera?.isPerspectiveCamera) return
const planes = this._cameraFrustumPlanes(camera)
const chan = this._observerConfig.actionsChannel
try {
await app.MessageBus.requestBusAction(
chan,
'SUBSCRIBEFRUSTUM',
{
planes,
frequency: this._observerConfig.subscribeFrequencyMs,
},
10000
)
} catch(err) {
console.warn('[Threetobus] SUBSCRIBEFRUSTUM failed:', err)
}
}
_cameraFrustumPlanes(camera) {
camera.updateMatrixWorld(true)
const matrix = new THREE.Matrix4().multiplyMatrices(
camera.projectionMatrix,
camera.matrixWorldInverse
)
const frustum = new THREE.Frustum().setFromProjectionMatrix(matrix)
return(frustum.planes.map(plane => ({
nx: plane.normal.x,
ny: plane.normal.y,
nz: plane.normal.z,
d: plane.constant,
})))
}
_ensurePlaceholderAgent(agentId) {
const geo = new THREE.BoxGeometry(0.4, 0.4, 0.4)
const mat = new THREE.MeshStandardMaterial({ color: 0x44aa88 })
const obj3D = new THREE.Mesh(geo, mat)
obj3D.name = agentId
this.tweensRegistry[agentId] = { move: null, rotate: null }
this.scene.add(obj3D)
return(obj3D)
}
initScene(options){ initScene(options){
// Scene // Scene
this.scene = new THREE.Scene() this.scene = new THREE.Scene()
+5 -4
View File
@@ -3369,10 +3369,11 @@ class DataGrid extends EicComponent {
let actions = ui.create(`<div class="cell actions"></div`); let actions = ui.create(`<div class="cell actions"></div`);
if(this.options.rowActions && Array.isArray(this.options.rowActions)) { if(this.options.rowActions && Array.isArray(this.options.rowActions)) {
for(let action of this.options.rowActions) { for(let action of this.options.rowActions) {
let button = ui.create(`<button eicbutton rounded title="${action.title ? action.title: ''}">${action.icon}</button>`); const severityAttr = action.severity ? ` ${action.severity}` : ''
button.addEventListener('click', action.callback.bind(row.getAttribute('data-id') != '' ? JSON.parse(row.getAttribute('data-id')): this)); let button = ui.create(`<button eicbutton rounded${severityAttr} title="${action.title ? action.title: ''}">${action.icon}</button>`)
actions.appendChild(button); button.addEventListener('click', action.callback.bind(row.getAttribute('data-id') != '' ? JSON.parse(row.getAttribute('data-id')): this))
actions.appendChild(button)
} }
} }
+4 -1
View File
@@ -26,7 +26,10 @@ class CreateSimView extends WindozDomContent {
this.models.sims.create({ this.models.sims.create({
kfId: this.outputs.keyframesSelector.value, kfId: this.outputs.keyframesSelector.value,
simName: this.outputs.simName.value simName: this.outputs.simName.value
}).then(data => ui.growl.append('Simulation created!','success',3000)) }).then(data => {
const simulationUuid = data?.payload?.simulationUuid ?? 'unknown'
ui.growl.append(`Simulation created (${simulationUuid})`, 'success', 4000)
})
this.unload() this.unload()
} }
} }
+12 -2
View File
@@ -1,8 +1,18 @@
<style>
.manage-sim > section {
height: 100%;
padding: 0;
}
.manage-sim .sim-list .row {
grid-template-columns: 2fr 2fr 1fr 6em 10em;
}
.manage-sim .sim-list .cell { text-align: center; }
</style>
<article eiccard class="manage-sim"> <article eiccard class="manage-sim">
<header> <header>
<h1>Play / Pause a simulation</h1> <h1>Play / Pause / Stop a simulation</h1>
</header> </header>
<section> <section>
<p>TODO: simulation play / pause controls</p> <div eicdatagrid class="sim-list"></div>
</section> </section>
</article> </article>
+192 -2
View File
@@ -1,14 +1,204 @@
class ManageSimView extends WindozDomContent { class ManageSimView extends WindozDomContent {
#lifecycleEventTypes = [
'onYourMarks',
'bigBang',
'simulationPaused',
'simulationResumed',
'simulationStopped',
'simulationPrepareFailed',
]
constructor() { constructor() {
super() super()
Object.assign(this, app.helpers.basicDialogs) Object.assign(this, app.helpers.activeAttributes, app.helpers.basicDialogs)
this.lifecycleChan = app.Assets.Store.json.busChannels.maestro.lifecycleChannel
this.lifecycleChan = this.lifecycleChan.replace(/\[UID\]/g, app.User.identity.uuid)
} }
async DOMContentLoaded(options) { async DOMContentLoaded(options) {
this.models = options.models this.models = options.models
this.wasBlured = false
this._refreshSeq = 0
this.lifecycleSubscribed = false
ui.eicfy(this.el) ui.eicfy(this.el)
// TODO: implement
const view = this
this.simGrid = new DataGrid(this.find('.sim-list'), {
headers: [
{ label: 'Simulation', sortable: true },
{ label: 'Primordial frame', sortable: true },
{ label: 'Owner', sortable: true },
{ label: 'Status', sortable: true },
{ label: '', sortable: false },
],
height: '480px',
rowActions: [
{
icon: '<i class="icon-play"></i>',
title: 'Start simulation',
severity: 'success',
callback: async function(event) {
await view.onPlaySim(this, event.currentTarget)
},
},
{
icon: '<i class="icon-pause"></i>',
title: 'Pause simulation',
severity: 'secondary',
callback: async function(event) {
await view.onPauseSim(this, event.currentTarget)
},
},
{
icon: '<i class="icon-stop"></i>',
title: 'Stop simulation',
severity: 'danger',
callback: async function(event) {
await view.onStopSim(this, event.currentTarget)
},
},
],
})
this.simGrid.enableFooter = false
await this.refreshSimList()
}
async DOMContentFocused() {
await this.subscribeLifecycle()
if(!this.wasBlured) return
this.wasBlured = false
this.refreshSimList()
}
async DOMContentBlured() {
this.wasBlured = true
await this.unsubscribeLifecycle()
}
async subscribeLifecycle() {
if(this.lifecycleSubscribed || !app.MessageBus?.connected) return
await app.MessageBus.subscribe([this.lifecycleChan])
for(const eventType of this.#lifecycleEventTypes) {
app.MessageBus.addBusListener(eventType, [this.lifecycleChan], this.lifecycleListener, 'ManageSimView')
}
this.lifecycleSubscribed = true
}
async unsubscribeLifecycle() {
if(!this.lifecycleSubscribed || !app.MessageBus?.connected) return
for(const eventType of this.#lifecycleEventTypes) {
app.MessageBus.removeBusListener(eventType, this.lifecycleListener, 'ManageSimView')
}
await app.MessageBus.unSubscribe([this.lifecycleChan])
this.lifecycleSubscribed = false
}
lifecycleListener(realChan, payload, sender) {
console.log('[ManageSimView] maestro lifecycle', { realChan, payload, sender })
}
async refreshSimList() {
const seq = ++this._refreshSeq
this.simGrid.loading = true
this.simGrid.clear()
try {
const data = await this.models.sims.list()
if(seq !== this._refreshSeq) return
const sims = data?.payload ?? []
this.simGrid.clear()
for(const sim of sims) {
const row = this.simGrid.addRow(
{ simulationUuid: sim.simulationUuid },
[
sim.sim_name ?? '',
sim.ekf_name ?? '',
sim.usr_name ?? '',
sim.state ?? 'idle',
],
true
)
this.updateSimButtons(sim.state ?? 'idle', row)
}
this.simGrid.updateFilters()
} finally {
if(seq === this._refreshSeq) this.simGrid.loading = false
}
}
updateSimButtons(state, rowEl) {
const buttons = rowEl?.querySelectorAll('.cell.actions button[eicbutton]')
if(!buttons || buttons.length < 3) return
const enabledByState = {
idle: [true, false, false],
preparing: [false, false, false],
live: [false, true, true],
paused: [true, false, true],
}
const enabled = enabledByState[state] ?? [false, false, false]
for(let i = 0; i < 3; i++) {
buttons[i].disabled = !enabled[i]
}
}
async onPlaySim(rowId, button) {
const simulationUuid = rowId?.simulationUuid
if(!simulationUuid) return
const row = button.closest('.row')
button.disabled = true
try {
const result = await this.models.sims.startSimulation(simulationUuid)
ui.growl.append(
`Simulation started (${result.agentIds?.length ?? 0} agent(s))`,
'success',
4000
)
await this.refreshSimList()
} catch(err) {
ui.growl.append(String(err), 'error', 6000)
const state = row?.querySelectorAll('.cell')[4]?.innerText?.trim() || 'idle'
this.updateSimButtons(state, row)
}
}
async onPauseSim(rowId, button) {
const simulationUuid = rowId?.simulationUuid
if(!simulationUuid) return
const row = button.closest('.row')
button.disabled = true
try {
await this.models.sims.pauseSimulation(simulationUuid)
ui.growl.append('Simulation paused', 'success', 3000)
await this.refreshSimList()
} catch(err) {
ui.growl.append(String(err), 'error', 6000)
const state = row?.querySelectorAll('.cell')[4]?.innerText?.trim() || 'idle'
this.updateSimButtons(state, row)
}
}
async onStopSim(rowId, button) {
const simulationUuid = rowId?.simulationUuid
if(!simulationUuid) return
const row = button.closest('.row')
button.disabled = true
try {
await this.models.sims.stopSimulation(simulationUuid)
ui.growl.append('Simulation stopped', 'success', 3000)
await this.refreshSimList()
} catch(err) {
ui.growl.append(String(err), 'error', 6000)
const state = row?.querySelectorAll('.cell')[4]?.innerText?.trim() || 'idle'
this.updateSimButtons(state, row)
}
} }
} }
+11 -4
View File
@@ -18,16 +18,20 @@ class SpaceView extends WindozDomContent {
//this.tileMarkup = app.Assets.Store.html['/app/assets/html/mailing/tile.html'] //this.tileMarkup = app.Assets.Store.html['/app/assets/html/mailing/tile.html']
} }
DOMContentFocused(options) { DOMContentFocused() {
if(this.wasBlured){ // Avoid 2nd refesh on DomContentLoaded if(this.wasBlured && this.viewMode === '3D' && this.ttb && this.renderingEngine) {
//this.refreshyoustuff() this.ttb.watchCameraFrustum(this.renderingEngine)
} }
this.wasBlured = false this.wasBlured = false
} }
DOMContentBlured(options) { this.wasBlured = true } DOMContentBlured() {
this.wasBlured = true
if(this.viewMode === '3D' && this.ttb) this.ttb.stopWatchingCameraFrustum()
}
DOMContentLoaded(options) { DOMContentLoaded(options) {
this.viewMode = options.mode
this.windowPrefsId = `live.spaceview.${options.mode}` this.windowPrefsId = `live.spaceview.${options.mode}`
for(let model in options.models) this[model] = options.models[model] for(let model in options.models) this[model] = options.models[model]
this.ttb = options.ttb this.ttb = options.ttb
@@ -35,6 +39,9 @@ class SpaceView extends WindozDomContent {
this.setupTriggers(components) this.setupTriggers(components)
this.setupRefs(components) this.setupRefs(components)
this.renderingEngine = this.ttb.startRendering(this.outputs.ttbCanvas, options.mode) this.renderingEngine = this.ttb.startRendering(this.outputs.ttbCanvas, options.mode)
if(options.mode === '3D') {
this.ttb.watchCameraFrustum(this.renderingEngine)
}
this.output('settingsMenu',app.Assets.Store.html.spaceViewSetting) this.output('settingsMenu',app.Assets.Store.html.spaceViewSetting)
this.outputs.settingsMenu.querySelectorAll('input[type="toggler"]').forEach(el => { this.outputs.settingsMenu.querySelectorAll('input[type="toggler"]').forEach(el => {
const tog = new InputToggler(el) const tog = new InputToggler(el)
+8
View File
@@ -95,3 +95,11 @@ way of coexist both actions and events.
Describe a proposal that stays close to what we have, but separates events from actions. Describe a proposal that stays close to what we have, but separates events from actions.
We have another issue: If I'm not mistaken, a front-end "sender" UID is actually his login UID.
That means that a user opening 2 browsers is viewed as the same sender on both.
Therefore, using sender as upsert key in the subscriptions registry of observer is problematic,
because we should allow the same user to view different things on different browsers.
Maybe in the second browser he's viewing another angle, or meybe he's not viewing this particular sim at all.
Back to business.
I slightly changed the listSims query, so we can display sim name, primordial frame name and owner name.