Angular constants via WebSocket
- new format for constants on the server - adaptions for the old client
This commit is contained in:
parent
b6f6d6f720
commit
8adaa6118a
15
client/src/app/core/services/constants.service.spec.ts
Normal file
15
client/src/app/core/services/constants.service.spec.ts
Normal file
@ -0,0 +1,15 @@
|
||||
import { TestBed, inject } from '@angular/core/testing';
|
||||
|
||||
import { ConstantsService } from './constants.service';
|
||||
|
||||
describe('ConstantsService', () => {
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
providers: [ConstantsService]
|
||||
});
|
||||
});
|
||||
|
||||
it('should be created', inject([ConstantsService], (service: ConstantsService) => {
|
||||
expect(service).toBeTruthy();
|
||||
}));
|
||||
});
|
95
client/src/app/core/services/constants.service.ts
Normal file
95
client/src/app/core/services/constants.service.ts
Normal file
@ -0,0 +1,95 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
|
||||
import { OpenSlidesComponent } from 'app/openslides.component';
|
||||
import { WebsocketService } from './websocket.service';
|
||||
import { Observable, of, Subject } from 'rxjs';
|
||||
|
||||
/**
|
||||
* constants have a key associated with the data.
|
||||
*/
|
||||
interface Constants {
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get constants from the server.
|
||||
*
|
||||
* @example
|
||||
* this.constantsService.get('OpenSlidesSettings').subscribe(constant => {
|
||||
* console.log(constant);
|
||||
* });
|
||||
*/
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class ConstantsService extends OpenSlidesComponent {
|
||||
/**
|
||||
* The constants
|
||||
*/
|
||||
private constants: Constants;
|
||||
|
||||
/**
|
||||
* Flag, if the websocket connection is open.
|
||||
*/
|
||||
private websocketOpen = false;
|
||||
|
||||
/**
|
||||
* Flag, if constants are requested, but the server hasn't send them yet.
|
||||
*/
|
||||
private pending = false;
|
||||
|
||||
/**
|
||||
* Pending requests will be notified by these subjects, one per key.
|
||||
*/
|
||||
private pendingSubject: { [key: string]: Subject<any> } = {};
|
||||
|
||||
/**
|
||||
* @param websocketService
|
||||
*/
|
||||
public constructor(private websocketService: WebsocketService) {
|
||||
super();
|
||||
|
||||
// The hook for recieving constants.
|
||||
websocketService.getOberservable<Constants>('constantsResponse').subscribe(constants => {
|
||||
this.constants = constants;
|
||||
if (this.pending) {
|
||||
// send constants to subscribers that await constants.
|
||||
this.pending = false;
|
||||
Object.keys(this.pendingSubject).forEach(key => {
|
||||
this.pendingSubject[key].next(this.constants[key]);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// We can request constants, if the websocket connection opens.
|
||||
websocketService.connectEvent.subscribe(() => {
|
||||
if (!this.websocketOpen && this.pending) {
|
||||
this.websocketService.send('constantsRequest', {});
|
||||
}
|
||||
this.websocketOpen = true;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the constant named by key.
|
||||
* @param key The constant to get.
|
||||
*/
|
||||
public get(key: string): Observable<any> {
|
||||
if (this.constants) {
|
||||
return of(this.constants[key]);
|
||||
} else {
|
||||
// we have to request constants.
|
||||
if (!this.pending) {
|
||||
this.pending = true;
|
||||
// if the connection is open, we directly can send the request.
|
||||
if (this.websocketOpen) {
|
||||
this.websocketService.send('constantsRequest', {});
|
||||
}
|
||||
}
|
||||
if (!this.pendingSubject[key]) {
|
||||
this.pendingSubject[key] = new Subject<any>();
|
||||
}
|
||||
return this.pendingSubject[key].asObservable();
|
||||
}
|
||||
}
|
||||
}
|
@ -38,7 +38,7 @@ export class OpenSlidesService extends OpenSlidesComponent {
|
||||
|
||||
// Handler that gets called, if the websocket connection reconnects after a disconnection.
|
||||
// There might have changed something on the server, so we check the operator, if he changed.
|
||||
websocketService.getReconnectObservable().subscribe(() => {
|
||||
websocketService.reconnectEvent.subscribe(() => {
|
||||
this.checkOperator();
|
||||
});
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { Injectable, NgZone } from '@angular/core';
|
||||
import { Injectable, NgZone, EventEmitter } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
import { Observable, Subject } from 'rxjs';
|
||||
import { MatSnackBar, MatSnackBarRef, SimpleSnackBar } from '@angular/material';
|
||||
@ -36,7 +36,26 @@ export class WebsocketService {
|
||||
/**
|
||||
* Subjects that will be called, if a reconnect was successful.
|
||||
*/
|
||||
private reconnectSubject: Subject<void>;
|
||||
private _reconnectEvent: EventEmitter<void> = new EventEmitter<void>();
|
||||
|
||||
/**
|
||||
* Getter for the reconnect event.
|
||||
*/
|
||||
public get reconnectEvent(): EventEmitter<void> {
|
||||
return this._reconnectEvent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Listeners will be nofitied, if the wesocket connection is establiched.
|
||||
*/
|
||||
private _connectEvent: EventEmitter<void> = new EventEmitter<void>();
|
||||
|
||||
/**
|
||||
* Getter for the connect event.
|
||||
*/
|
||||
public get connectEvent(): EventEmitter<void> {
|
||||
return this._connectEvent;
|
||||
}
|
||||
|
||||
/**
|
||||
* The websocket.
|
||||
@ -57,9 +76,7 @@ export class WebsocketService {
|
||||
private matSnackBar: MatSnackBar,
|
||||
private zone: NgZone,
|
||||
public translate: TranslateService
|
||||
) {
|
||||
this.reconnectSubject = new Subject<void>();
|
||||
}
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Creates a new WebSocket connection and handles incomming events.
|
||||
@ -91,8 +108,9 @@ export class WebsocketService {
|
||||
this.connectionErrorNotice.dismiss();
|
||||
this.connectionErrorNotice = null;
|
||||
}
|
||||
this.reconnectSubject.next();
|
||||
this._reconnectEvent.emit();
|
||||
}
|
||||
this._connectEvent.emit();
|
||||
});
|
||||
};
|
||||
|
||||
@ -156,13 +174,6 @@ export class WebsocketService {
|
||||
return this.subjects[type].asObservable();
|
||||
}
|
||||
|
||||
/**
|
||||
* get the reconnect observable. It will be published, if a reconnect was sucessful.
|
||||
*/
|
||||
public getReconnectObservable(): Observable<void> {
|
||||
return this.reconnectSubject.asObservable();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a message to the server with the content and the given type.
|
||||
*
|
||||
|
@ -47,14 +47,11 @@ class AssignmentsAppConfig(AppConfig):
|
||||
|
||||
def get_angular_constants(self):
|
||||
assignment = self.get_model('Assignment')
|
||||
InnerItem = TypedDict('InnerItem', {'value': int, 'display_name': str})
|
||||
Item = TypedDict('Item', {'name': str, 'value': List[InnerItem]})
|
||||
data: Item = {
|
||||
'name': 'AssignmentPhases',
|
||||
'value': []}
|
||||
Item = TypedDict('Item', {'value': int, 'display_name': str})
|
||||
phases: List[Item] = []
|
||||
for phase in assignment.PHASES:
|
||||
data['value'].append({
|
||||
phases.append({
|
||||
'value': phase[0],
|
||||
'display_name': phase[1],
|
||||
})
|
||||
return [data]
|
||||
return {'AssignmentPhases': phases}
|
||||
|
@ -1,6 +1,6 @@
|
||||
from collections import OrderedDict
|
||||
from operator import attrgetter
|
||||
from typing import Any, List
|
||||
from typing import Any, Dict, List
|
||||
|
||||
from django.apps import AppConfig
|
||||
from django.conf import settings
|
||||
@ -74,6 +74,8 @@ class CoreAppConfig(AppConfig):
|
||||
def get_angular_constants(self):
|
||||
from .config import config
|
||||
|
||||
constants: Dict[str, Any] = {}
|
||||
|
||||
# Client settings
|
||||
client_settings_keys = [
|
||||
'MOTION_IDENTIFIER_MIN_DIGITS',
|
||||
@ -88,9 +90,7 @@ class CoreAppConfig(AppConfig):
|
||||
# Settings key does not exist. Do nothing. The client will
|
||||
# treat this as undefined.
|
||||
pass
|
||||
client_settings = {
|
||||
'name': 'OpenSlidesSettings',
|
||||
'value': client_settings_dict}
|
||||
constants['OpenSlidesSettings'] = client_settings_dict
|
||||
|
||||
# Config variables
|
||||
config_groups: List[Any] = []
|
||||
@ -110,17 +110,13 @@ class CoreAppConfig(AppConfig):
|
||||
items=[]))
|
||||
# Add the config variable to the current group and subgroup.
|
||||
config_groups[-1]['subgroups'][-1]['items'].append(config_variable.data)
|
||||
config_variables = {
|
||||
'name': 'OpenSlidesConfigVariables',
|
||||
'value': config_groups}
|
||||
constants['OpenSlidesConfigVariables'] = config_groups
|
||||
|
||||
# Send the privacy policy to the client. A user should view them, even he is
|
||||
# not logged in (so does not have the config values yet).
|
||||
privacy_policy = {
|
||||
'name': 'PrivacyPolicy',
|
||||
'value': config['general_event_privacy_policy']}
|
||||
constants['PrivacyPolicy'] = config['general_event_privacy_policy']
|
||||
|
||||
return [client_settings, config_variables, privacy_policy]
|
||||
return constants
|
||||
|
||||
|
||||
def call_save_default_values(**kwargs):
|
||||
|
@ -141,10 +141,9 @@ class WebclientJavaScriptView(utils_views.View):
|
||||
except AttributeError:
|
||||
# The app doesn't have this method. Continue to next app.
|
||||
continue
|
||||
for constant in get_angular_constants():
|
||||
value = json.dumps(constant['value'])
|
||||
name = constant['name']
|
||||
angular_constants += ".constant('{}', {})".format(name, value)
|
||||
for key, value in get_angular_constants().items():
|
||||
value = json.dumps(value)
|
||||
angular_constants += ".constant('{}', {})".format(key, value)
|
||||
|
||||
# Use JavaScript loadScript function from
|
||||
# http://balpha.de/2011/10/jquery-script-insertion-and-its-consequences-for-debugging/
|
||||
|
@ -58,7 +58,4 @@ class UsersAppConfig(AppConfig):
|
||||
permissions.append({
|
||||
'display_name': permission.name,
|
||||
'value': '.'.join((permission.content_type.app_label, permission.codename,))})
|
||||
permission_settings = {
|
||||
'name': 'permissions',
|
||||
'value': permissions}
|
||||
return [permission_settings]
|
||||
return {'permissions': permissions}
|
||||
|
@ -4,6 +4,7 @@ import jsonschema
|
||||
from asgiref.sync import sync_to_async
|
||||
from channels.db import database_sync_to_async
|
||||
from channels.generic.websocket import AsyncJsonWebsocketConsumer
|
||||
from django.apps import apps
|
||||
|
||||
from ..core.config import config
|
||||
from ..core.models import Projector
|
||||
@ -30,7 +31,7 @@ class ProtocollAsyncJsonWebsocketConsumer(AsyncJsonWebsocketConsumer):
|
||||
"type": {
|
||||
"description": "Defines what kind of packages is packed.",
|
||||
"type": "string",
|
||||
"pattern": "notify", # The server can sent other types
|
||||
"pattern": "notify|constantsRequest", # The server can sent other types
|
||||
},
|
||||
"content": {
|
||||
"description": "The content of the package.",
|
||||
@ -136,6 +137,19 @@ class SiteConsumer(ProtocollAsyncJsonWebsocketConsumer):
|
||||
)
|
||||
else:
|
||||
await self.send_json(type='error', content='Invalid notify message', in_response=id)
|
||||
elif type == 'constantsRequest':
|
||||
angular_constants: Dict[str, Any] = {}
|
||||
for app in apps.get_app_configs():
|
||||
try:
|
||||
# Each app can deliver values to angular when implementing this method.
|
||||
# It should return a list with dicts containing the 'name' and 'value'.
|
||||
get_angular_constants = app.get_angular_constants
|
||||
except AttributeError:
|
||||
# The app doesn't have this method. Continue to next app.
|
||||
continue
|
||||
constants = await database_sync_to_async(get_angular_constants)()
|
||||
angular_constants.update(constants)
|
||||
await self.send_json(type='constantsResponse', content=angular_constants, in_response=id)
|
||||
|
||||
async def send_notify(self, event: Dict[str, Any]) -> None:
|
||||
"""
|
||||
|
@ -94,17 +94,17 @@ class WebclientJavaScriptView(TestCase):
|
||||
response = self.client.get(reverse('core_webclient_javascript', args=['site']))
|
||||
content = response.content.decode()
|
||||
constants = self.get_angular_constants_from_apps()
|
||||
for constant in constants:
|
||||
self.assertTrue(json.dumps(constant['value']) in content)
|
||||
for key, constant in constants.items():
|
||||
self.assertTrue(json.dumps(constant) in content)
|
||||
|
||||
def get_angular_constants_from_apps(self):
|
||||
constants = []
|
||||
constants = {}
|
||||
for app in apps.get_app_configs():
|
||||
try:
|
||||
get_angular_constants = app.get_angular_constants
|
||||
except AttributeError:
|
||||
continue
|
||||
constants.extend(get_angular_constants())
|
||||
constants.update(get_angular_constants())
|
||||
return constants
|
||||
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user