Versões recentes do Ext JS, especialmente o Modern Toolkit, diminuíram o limite de entrada na estrutura (exemplos do Kitchen Sink), simplificaram a criação da interface desejada (oi Sencha Architect) e atingiram um tamanho mínimo para aplicativos da web (Sencha Cmd).
Talvez Habr deva ser diluído com um exemplo da implementação de um “centro situacional”, onde em tempo real você pode assistir câmeras e eventos a partir deles (todos os dados são falsos).

Vamos começar. Vamos criar um projeto Spring Boot com 2 controladores que fornecerão uma lista de câmeras, eventos existentes e a capacidade de se inscrever em novos eventos (via WebSocket).

Em seguida, adicione os modelos, armazenamentos, visualizações e dependências externas necessárias:

Mais detalhadamente, a visualização do mapa interagindo com o componente React do mapa.Ext.define('Cameras.view.override.Map', { override: 'Cameras.view.Map', config: { cameras: {}, react: null }, initialize: function() { let that = this; let e = React.createElement; this.setReact(ReactDOM.render(e(createReactClass({ getInitialState: function() { return { items: [], center: [55.751574, 37.573856]}; }, render: function() { let placemarks = []; for(let i=0; i < this.state.items.length; i++) { let location = this.state.items[i].get("location"); placemarks.push(e(window.ReactYandexMaps.Placemark, { geometry: [location.latitude, location.longitude], options: { preset: 'islands#blueCircleDotIconWithCaption', iconCaptionMaxWidth: '50' } })); } let map = e(window.ReactYandexMaps.Map, { state: { center: this.state.center, zoom: 10 }, width: '100%', height: '100%' }, placemarks); return e(window.ReactYandexMaps.YMaps, null, map); } })), this.mapContainer.dom)); }, getElementConfig: function() { return { reference: 'element', className: 'x-container', children: [{ reference: 'bodyElement', style: 'width: 100%; height: 100%', className: 'x-inner', children: [{ style: 'width: 100%; height: 100%', reference: 'mapContainer', className: Ext.baseCSSPrefix + 'map-container' }] }] }; }, addCamera: function(cameraModel) { if(!this.containsCamera(cameraModel)) { this.getCameras()[cameraModel.get("id")] = cameraModel; this.getReact().setState({ items: Object.values(this.getCameras()) }); this.fitCamera(cameraModel); } }, removeCamera: function(cameraModel) { if(this.containsCamera(cameraModel)) { delete this.getCameras()[cameraModel.get("id")]; this.getReact().setState({ items: Object.values(this.getCameras()) }); } }, fitCamera: function(cameraModel) { if(this.containsCamera(cameraModel)) { let location = this.getCameras()[cameraModel.get("id")].get("location"); this.getReact().setState({ center: [location.latitude, location.longitude] }); } }, privates: { containsCamera: function(cameraModel) { cameraId = "" + cameraModel.get("id"); return Object.keys(this.getCameras()).includes(cameraId); } } });
Também concordamos que os eventos virão do componente CamerasGrid, conforme este componente é responsável por adicionar / remover as câmeras do cartão.
Controlador de componente CamerasGrid que adiciona geração de eventos ao componente Ext.define('Cameras.view.CamerasGridViewController', { extend: 'Ext.app.ViewController', alias: 'controller.camerasgrid', init: function() { let socket = new WebSocket("ws://localhost:8080/events/sub"); socket.onopen = function(e) { console.log('onopen'); }; socket.onmessage = this.onMessage.bind(this); }, onMessage: function(event) { let data = Ext.decode(event.data); let gridData = this.getView().getStore().getData(); for(let i=0; i < gridData.length; i++) { let checked = gridData.getAt(i).get("checked"); if(checked !== undefined && checked) { if(gridData.getAt(i).get("id") == data.camera.id) { this.fireViewEvent("cameraRecognition", data); } } } } });
O resultado é uma interface bastante divertida. Observo que, com o design adequado da arquitetura do aplicativo (mesmo uma pequena), o tempo para criar uma é muito pequeno, podendo demorar algumas horas.

O código de exemplo está disponível no
github.com .