From 9442ff5199be8170e31c4d11c97b86b49d2a6f33 Mon Sep 17 00:00:00 2001
From: Felix Riehm <mail@felixriehm.de>
Date: Thu, 14 Mar 2024 14:07:18 +0100
Subject: [PATCH] add carousel for map

---
 web/src/canvas/canvas.ts                | 10 ++++-
 web/src/canvas/carousel.ts              | 16 +++----
 web/src/canvas/geo/circle.ts            | 11 +++--
 web/src/canvas/geo/circles_layer.ts     | 14 +++---
 web/src/canvas/geo/geoCanvas.ts         | 60 ++++++++++++++++++-------
 web/src/canvas/listen/listenToCanvas.ts | 11 +----
 web/src/canvas/progress_indicator.ts    |  9 ++--
 web/src/canvas/transition.ts            | 22 ++++++---
 web/src/main.ts                         |  2 +-
 9 files changed, 98 insertions(+), 57 deletions(-)

diff --git a/web/src/canvas/canvas.ts b/web/src/canvas/canvas.ts
index 7376eea..e519271 100644
--- a/web/src/canvas/canvas.ts
+++ b/web/src/canvas/canvas.ts
@@ -1,20 +1,23 @@
 import {select, Selection} from "d3";
-import {Subject} from "rxjs";
+import {BehaviorSubject, Subject} from "rxjs";
 import {CircleData, DatabaseInfo, NetworkInfoboxData} from "../observable/model";
 import {SettingsData} from "../configuration/hatnote_settings";
 import {InfoBox, InfoboxType} from "./info_box";
 import {CirclesLayer} from "./listen/circles_layer";
 import {Header} from "./header";
 import {Theme} from "../theme/theme";
+import {HatnoteVisService} from "../service_event/model";
 
 export abstract class Canvas {
     abstract header: Header;
     public readonly theme: Theme;
     abstract info_box_websocket: InfoBox;
     abstract info_box_legend: InfoBox;
+    public readonly isMobileScreen: boolean = false;
     public readonly settings: SettingsData;
     public readonly showNetworkInfoboxObservable: Subject<NetworkInfoboxData>
     public readonly updateDatabaseInfoSubject: Subject<DatabaseInfo>
+    public readonly hatnoteVisServiceChangedSubject: BehaviorSubject<HatnoteVisService>
     public readonly updateVersionSubject: Subject<[string, number]>
     public readonly newCircleSubject: Subject<CircleData>
     public readonly appContainer:  Selection<HTMLDivElement, unknown, null, undefined>;
@@ -40,17 +43,22 @@ export abstract class Canvas {
     protected constructor(theme: Theme, settings: SettingsData, newCircleSubject: Subject<CircleData>,
                           showNetworkInfoboxObservable: Subject<NetworkInfoboxData>,
                           updateVersionSubject: Subject<[string, number]>,
+                          hatnoteVisServiceChangedSubject: BehaviorSubject<HatnoteVisService>,
                           updateDatabaseInfoSubject: Subject<DatabaseInfo>,
                           appContainer:  Selection<HTMLDivElement, unknown, null, undefined>) {
         this._width = window.innerWidth;
         this._height = window.innerHeight;
         this.theme = theme;
         this.settings = settings
+        this.hatnoteVisServiceChangedSubject = hatnoteVisServiceChangedSubject
         this.newCircleSubject = newCircleSubject
         this.showNetworkInfoboxObservable = showNetworkInfoboxObservable
         this.updateDatabaseInfoSubject = updateDatabaseInfoSubject
         this.updateVersionSubject = updateVersionSubject
         this.appContainer = appContainer;
+        if (this.width <= 430 || this.height <= 430) { // iPhone 12 Pro Max 430px viewport width
+            this.isMobileScreen = true;
+        }
     }
 
     public appendSVGElement(type: string): Selection<SVGGElement, unknown, null, any> {
diff --git a/web/src/canvas/carousel.ts b/web/src/canvas/carousel.ts
index 9b140bc..273d74e 100644
--- a/web/src/canvas/carousel.ts
+++ b/web/src/canvas/carousel.ts
@@ -5,6 +5,7 @@ import {DatabaseInfo} from "../observable/model";
 import {Subject} from "rxjs";
 import {HatnoteVisService} from "../service_event/model";
 import {ServiceTheme} from "../theme/model";
+import {GeoCanvas} from "./geo/geoCanvas";
 
 export class Carousel {
     public readonly transition: Transition;
@@ -14,11 +15,13 @@ export class Carousel {
     public serviceError: Map<HatnoteVisService, boolean>
     public allServicesHaveError: boolean
     private startCarouselService: HatnoteVisService | null
-    private readonly canvas: ListenToCanvas
+    private readonly canvas: ListenToCanvas | GeoCanvas
     private currentCarouselOrderIndex;
-    constructor(canvas: ListenToCanvas) {
+    constructor(canvas: ListenToCanvas | GeoCanvas) {
         this.canvas = canvas
         this.transition = new Transition(this.canvas)
+        this.transition.onTransitionMid.subscribe(_ => this.canvas.hatnoteVisServiceChangedSubject.next(this.canvas.theme.current_service_theme.id_name))
+        this.transition.onTransitionEnd.subscribe(_ => this.continueCarousel())
         this.progess_indicator = new ProgressIndicator(this.canvas)
         this.updateDatabaseInfoSubject = this.canvas.updateDatabaseInfoSubject
         this.serviceError = new Map<HatnoteVisService, boolean>()
@@ -75,9 +78,7 @@ export class Carousel {
 
                     if(dbInfo.service === this.canvas.theme.current_service_theme.id_name && !this.allServicesHaveError) {
                         this.initNextTheme()
-                        this.transition.startTransition(this.canvas.theme.current_service_theme,
-                            (_) => this.canvas.renderCurrentTheme(),
-                            (_) => this.continueCarousel())
+                        this.transition.startTransition(this.canvas.theme.current_service_theme)
                     }
                     return;
                 }
@@ -110,7 +111,6 @@ export class Carousel {
         if(nextTheme){
             this.canvas.theme.set_current_theme(nextTheme);
             this.progess_indicator.setCurrentServiceIndicator(nextTheme)
-            this.canvas.hatnoteVisServiceChangedSubject.next(this.canvas.theme.current_service_theme.id_name)
         }
     }
 
@@ -134,9 +134,7 @@ export class Carousel {
             indicator?.start(() => {
                 if (!this.allServicesHaveError) {
                     this.initNextTheme()
-                    this.transition.startTransition(this.canvas.theme.current_service_theme,
-                        (_) => this.canvas.renderCurrentTheme(),
-                        (_) => this.continueCarousel())
+                    this.transition.startTransition(this.canvas.theme.current_service_theme)
                 }
             })
         }
diff --git a/web/src/canvas/geo/circle.ts b/web/src/canvas/geo/circle.ts
index ec03b87..d4f6eab 100644
--- a/web/src/canvas/geo/circle.ts
+++ b/web/src/canvas/geo/circle.ts
@@ -1,6 +1,6 @@
 import {CirclesLayer} from "./circles_layer";
 import {HatnoteVisService} from "../../service_event/model";
-import {BaseType, GeoProjection, select, Selection} from "d3";
+import {BaseType, select, Selection} from "d3";
 import {CircleData} from "../../observable/model";
 
 export class Circle{
@@ -8,11 +8,16 @@ export class Circle{
     private readonly root:  Selection<SVGCircleElement, unknown, null, undefined>;
 
     constructor(circlesLayer: CirclesLayer, circleData: CircleData,
-                svgCircle:  SVGCircleElement, projection: GeoProjection, service: HatnoteVisService) {
+                svgCircle:  SVGCircleElement, service: HatnoteVisService) {
         this.circlesLayer = circlesLayer
         // init circle values
         this.root = select(svgCircle)
-        const point = projection([circleData.location?.coordinate.long ?? 0, circleData.location?.coordinate.lat?? 0])
+        let point;
+        if(this.circlesLayer.canvas.theme.current_service_theme.id_name === HatnoteVisService.Bloxberg){
+            point = this.circlesLayer.worldProjection([circleData.location?.coordinate.long ?? 0, circleData.location?.coordinate.lat?? 0])
+        } else {
+            point = this.circlesLayer.germanyProjection([circleData.location?.coordinate.long ?? 0, circleData.location?.coordinate.lat?? 0])
+        }
         if(point === null || circleData.location === undefined){
             return
         }
diff --git a/web/src/canvas/geo/circles_layer.ts b/web/src/canvas/geo/circles_layer.ts
index ff665df..9397efa 100644
--- a/web/src/canvas/geo/circles_layer.ts
+++ b/web/src/canvas/geo/circles_layer.ts
@@ -8,18 +8,20 @@ import {ServiceEvent} from "../../service_event/model";
 export class CirclesLayer{
     private readonly root: Selection<SVGGElement, unknown, null, any>;
     public readonly canvas: Canvas
-    public readonly projection: GeoProjection
+    public readonly germanyProjection: GeoProjection
+    public readonly worldProjection: GeoProjection
 
-    constructor(canvas: Canvas, projection: GeoProjection) {
+    constructor(canvas: Canvas, germanyProjection: GeoProjection, worldProjection: GeoProjection) {
         this.canvas = canvas
-        this.projection = projection
+        this.germanyProjection = germanyProjection;
+        this.worldProjection = worldProjection;
         this.root = canvas.appendSVGElement('g').attr('id', 'circle_layer')
         canvas.newCircleSubject.subscribe({
-            next: (value) => this.addCircle(value, this.projection)
+            next: (value) => this.addCircle(value)
         })
     }
 
-    private addCircle(circle: CircleData, projection: GeoProjection){
+    private addCircle(circle: CircleData){
         let that = this;
 
         // make sure that circle that already exits a removed so that the animation can start from start
@@ -34,7 +36,7 @@ export class CirclesLayer{
                 }
 
                 new Circle(that,circleData,
-                    this, that.projection, service)
+                    this, service)
             })
     }
 
diff --git a/web/src/canvas/geo/geoCanvas.ts b/web/src/canvas/geo/geoCanvas.ts
index 3f60f76..a198528 100644
--- a/web/src/canvas/geo/geoCanvas.ts
+++ b/web/src/canvas/geo/geoCanvas.ts
@@ -5,7 +5,7 @@ import {CirclesLayer} from "./circles_layer";
 import {Header} from "../header";
 import {InfoBox, InfoboxType} from "../info_box";
 import {Theme} from "../../theme/theme";
-import {Subject} from "rxjs";
+import {BehaviorSubject, Subject} from "rxjs";
 import {CircleData, DatabaseInfo, NetworkInfoboxData} from "../../observable/model";
 import {SettingsData} from "../../configuration/hatnote_settings";
 import {feature, mesh} from "topojson";
@@ -15,6 +15,7 @@ import {GeometryObject, Topology} from 'topojson-specification';
 import {FeatureCollection, GeoJsonProperties} from 'geojson';
 import {Canvas} from "../canvas";
 import {HatnoteVisService} from "../../service_event/model";
+import {Carousel} from "../carousel";
 
 export class GeoCanvas extends Canvas{
     public readonly  circles_layer: CirclesLayer
@@ -22,35 +23,48 @@ export class GeoCanvas extends Canvas{
     protected readonly _root: Selection<SVGSVGElement, unknown, null, any>;
     public readonly  info_box_websocket: InfoBox;
     public readonly  info_box_legend: InfoBox;
-    public world_map: any;
+    public readonly worldMap: Selection<SVGGElement, unknown, null, any>;
+    public readonly worldMapProjection: GeoProjection;
+    public readonly germanyMap: Selection<SVGGElement, unknown, null, any>;
+    public readonly germanyMapProjection: GeoProjection;
+    public readonly carousel: Carousel | undefined
 
     constructor(theme: Theme, settings: SettingsData, newCircleSubject: Subject<CircleData>,
                 showNetworkInfoboxObservable: Subject<NetworkInfoboxData>,
                 updateVersionSubject: Subject<[string, number]>,
+                hatnoteVisServiceChangedSubject: BehaviorSubject<HatnoteVisService>,
                 updateDatabaseInfoSubject: Subject<DatabaseInfo>,
                 appContainer:  Selection<HTMLDivElement, unknown, null, undefined>) {
-        super(theme, settings, newCircleSubject, showNetworkInfoboxObservable, updateVersionSubject,updateDatabaseInfoSubject, appContainer)
+        super(theme, settings, newCircleSubject, showNetworkInfoboxObservable, updateVersionSubject,hatnoteVisServiceChangedSubject, updateDatabaseInfoSubject, appContainer)
 
-        // draw order matters in this function. Do not change without checking the result.
 
+        // draw order matters in this function. Do not change without checking the result.
         this._root = appContainer.append("svg")
             .attr("id", 'hatnote-canvas')
             .attr("width", this.width)
             .attr("height", this.height)
             .attr("style", "max-width: 100%; height: auto;");
 
-        let projection;
-        if(this.theme.current_service_theme.id_name === HatnoteVisService.Bloxberg){
-            projection = this.initWorldMapSvg()
-        } else {
-            projection = this.initGermanyMapSvg()
-        }
-        this.circles_layer = new CirclesLayer(this, projection)
+        this.worldMap = this._root.append("g").attr("id", "world-map")
+        this.germanyMap = this._root.append("g").attr("id", "germany-map")
+        this.worldMapProjection = this.initWorldMapSvg()
+        this.germanyMapProjection = this.initGermanyMapSvg()
+        this.circles_layer = new CirclesLayer(this, this.germanyMapProjection, this.worldMapProjection)
         this.header = new Header(this, false)
         // needs to be added last to the svg because it should draw over everything else
         this.info_box_websocket = new InfoBox(this, InfoboxType.network_websocket_connecting, false, undefined, undefined)
         this.info_box_legend = new InfoBox(this, InfoboxType.legend, false, undefined, undefined)
 
+        if(settings.carousel_mode && !this.isMobileScreen){
+            this.carousel = new Carousel(this)
+        }
+
+        this.hatnoteVisServiceChangedSubject.subscribe({
+            next: (value) => {
+                this.renderCurrentTheme()
+            }
+        })
+
         this.renderCurrentTheme();
 
         window.onresize = (_) => this.windowUpdate();
@@ -95,7 +109,8 @@ export class GeoCanvas extends Canvas{
         const path = geoPath(projection);
 
         // Add a path for each country and color it according te this data.
-        this._root.append("g")
+        this.germanyMap.append("g")
+            .attr("id", "countries-mesh")
             .selectAll("path")
             .data((states as FeatureCollection).features)
             .join("path")
@@ -106,7 +121,8 @@ export class GeoCanvas extends Canvas{
 
         let countrymesh = mesh(germany, statesGeometry as GeometryObject, (a: GeometryObject, b: GeometryObject) => a !== b)
         // Add a white mesh.
-        this._root.append("path")
+        this.germanyMap.append("path")
+            .attr("id", "countries-border-mesh")
             .datum(countrymesh)
             .attr("fill", "none")
             .attr("stroke", "white")
@@ -128,7 +144,8 @@ export class GeoCanvas extends Canvas{
 
         // draw order matters here, check before changing something
         // Add a white sphere with a black border.
-        this._root.append("path")
+        this.worldMap.append("path")
+            .attr("id", "black-world-boundary")
             .datum({type: "Sphere"})
             .attr("fill", "white")
             .attr("stroke", "currentColor")
@@ -139,7 +156,8 @@ export class GeoCanvas extends Canvas{
         let countriesGeometry: GeometryObject<GeoJsonProperties> = world.objects.countries;
         let countries = feature(world, countriesGeometry)
         // Add a path for each country and color it according te this data.
-        this._root.append("g")
+        this.worldMap.append("g")
+            .attr("id", "countries-mesh")
             .selectAll("path")
             .data((countries as FeatureCollection).features)
             .join("path")
@@ -150,7 +168,8 @@ export class GeoCanvas extends Canvas{
 
         let countrymesh = mesh(world, countriesGeometry as GeometryObject, (a: GeometryObject, b: GeometryObject) => a !== b)
         // Add a white mesh.
-        this._root.append("path")
+        this.worldMap.append("path")
+            .attr("id", "countries-border-mesh")
             .datum(countrymesh)
             .attr("fill", "none")
             .attr("stroke", "white")
@@ -160,11 +179,18 @@ export class GeoCanvas extends Canvas{
     }
 
     public renderCurrentTheme(){
+        if (this.theme.current_service_theme.id_name == HatnoteVisService.Bloxberg) {
+            this.worldMap.attr("opacity", 1)
+            this.germanyMap.attr("opacity", 0)
+        } else {
+            this.worldMap.attr("opacity", 0)
+            this.germanyMap.attr("opacity", 1)
+        }
+
         // remove circles from other services
         this.circles_layer.removeOtherServiceCircles(this.theme.current_service_theme)
 
         // update header logo
-        console.log(this.theme.current_service_theme.name)
         this.header.themeUpdate(this.theme.current_service_theme)
     }
 
diff --git a/web/src/canvas/listen/listenToCanvas.ts b/web/src/canvas/listen/listenToCanvas.ts
index 3be1385..4a86464 100644
--- a/web/src/canvas/listen/listenToCanvas.ts
+++ b/web/src/canvas/listen/listenToCanvas.ts
@@ -23,15 +23,12 @@ export class ListenToCanvas extends Canvas {
     public readonly header: Header;
     protected readonly _root: Selection<SVGSVGElement, unknown, null, any>;
     public readonly navigation: Navigation | undefined;
-    public readonly isMobileScreen: boolean = false;
     public readonly info_box_websocket: InfoBox;
     public readonly info_box_audio: InfoBox;
     public readonly info_box_legend: InfoBox;
     public readonly mute_icon: MuteIcon;
     public readonly newBannerSubject: Subject<BannerData>
-    public readonly hatnoteVisServiceChangedSubject: BehaviorSubject<HatnoteVisService>
     public readonly showAudioInfoboxObservable: Subject<boolean>
-    public readonly showNetworkInfoboxObservable: Subject<NetworkInfoboxData>
     public readonly carousel: Carousel | undefined
 
     constructor(theme: Theme, settings: SettingsData, newCircleSubject: Subject<CircleData>,
@@ -42,16 +39,10 @@ export class ListenToCanvas extends Canvas {
                 hatnoteVisServiceChangedSubject: BehaviorSubject<HatnoteVisService>,
                 updateDatabaseInfoSubject: Subject<DatabaseInfo>,
                 appContainer:  Selection<HTMLDivElement, unknown, null, undefined>){
-        super(theme, settings, newCircleSubject, showNetworkInfoboxObservable, updateVersionSubject,updateDatabaseInfoSubject, appContainer)
+        super(theme, settings, newCircleSubject, showNetworkInfoboxObservable, updateVersionSubject,hatnoteVisServiceChangedSubject,updateDatabaseInfoSubject, appContainer)
 
         this.newBannerSubject = newBannerSubject
         this.showAudioInfoboxObservable = showAudioInfoboxObservable
-        this.showNetworkInfoboxObservable = showNetworkInfoboxObservable
-        this.hatnoteVisServiceChangedSubject = hatnoteVisServiceChangedSubject
-
-        if (this.width <= 430 || this.height <= 430) { // iPhone 12 Pro Max 430px viewport width
-            this.isMobileScreen = true;
-        }
 
         // draw order matters in this function. Do not change without checking the result.
         this._root = this.appContainer.append("svg")
diff --git a/web/src/canvas/progress_indicator.ts b/web/src/canvas/progress_indicator.ts
index f6719a5..0b595ba 100644
--- a/web/src/canvas/progress_indicator.ts
+++ b/web/src/canvas/progress_indicator.ts
@@ -2,6 +2,7 @@ import { easeLinear, Selection, transition} from "d3";
 import {ListenToCanvas} from "./listen/listenToCanvas";
 import {ServiceTheme} from "../theme/model";
 import {HatnoteVisService} from "../service_event/model";
+import {GeoCanvas} from "./geo/geoCanvas";
 
 export class ProgressIndicator{
     public readonly service_indicators: Map<HatnoteVisService, ServiceProgressIndicator>;
@@ -11,9 +12,9 @@ export class ProgressIndicator{
         return this._currentServiceIndicator;
     }
 
-    private readonly canvas: ListenToCanvas
+    private readonly canvas: ListenToCanvas | GeoCanvas
 
-    constructor(canvas: ListenToCanvas) {
+    constructor(canvas: ListenToCanvas | GeoCanvas) {
         this.canvas = canvas
         this.service_indicators = new Map<HatnoteVisService, ServiceProgressIndicator>()
         canvas.theme.service_themes.forEach(service => {
@@ -41,9 +42,9 @@ class ServiceProgressIndicator{
     private readonly textBox: Selection<SVGRectElement, unknown, null, any>
     private readonly text: Selection<SVGTextElement, unknown, null, any>
     public readonly service_id: HatnoteVisService
-    private readonly canvas: ListenToCanvas
+    private readonly canvas: ListenToCanvas | GeoCanvas
 
-    constructor(canvas: ListenToCanvas, service_theme: ServiceTheme) {
+    constructor(canvas: ListenToCanvas | GeoCanvas, service_theme: ServiceTheme) {
         this.canvas = canvas;
         let progress_indicator_width = canvas.theme.progress_indicator_width(canvas.width);
         let pos_x = canvas.theme.progress_indicator_pos_x(service_theme.id_name, canvas.width, progress_indicator_width, canvas.theme.progress_indicator_gap_width);
diff --git a/web/src/canvas/transition.ts b/web/src/canvas/transition.ts
index 563d816..a00c8b7 100644
--- a/web/src/canvas/transition.ts
+++ b/web/src/canvas/transition.ts
@@ -3,6 +3,9 @@ import {ListenToCanvas} from "./listen/listenToCanvas";
 import MpdlLogo from "../../assets/images/logo-mpdl-twocolor-dark-var1.png";
 import {ServiceTheme} from "../theme/model";
 import {HatnoteVisService} from "../service_event/model";
+import {GeoCanvas} from "./geo/geoCanvas";
+import {Subject} from "rxjs";
+import {NetworkInfoboxData} from "../observable/model";
 
 export class Transition{
     private readonly root: Selection<SVGGElement, unknown, null, any>;
@@ -17,9 +20,12 @@ export class Transition{
     private readonly mpdl_logo:  Selection<SVGImageElement, unknown, null, any>;
     private readonly text: Selection<SVGTextElement, unknown, null, any>;
     private readonly service_logo: Selection<SVGImageElement, unknown, null, any>;
-    private readonly canvas: ListenToCanvas;
+    private readonly canvas: ListenToCanvas | GeoCanvas;
+    public readonly onTransitionStart: Subject<void>
+    public readonly onTransitionMid: Subject<void>
+    public readonly onTransitionEnd: Subject<void>
 
-    constructor(canvas: ListenToCanvas) {
+    constructor(canvas: ListenToCanvas | GeoCanvas) {
         this.canvas = canvas
         this.root = canvas.appendSVGElement('g').attr('id', 'transition_layer').attr('opacity', 0)
 
@@ -53,11 +59,15 @@ export class Transition{
             .text('Next service:')
 
         this.service_logo = this.transition_screen.append('image')
+
+        this.onTransitionStart = new Subject()
+        this.onTransitionMid = new Subject()
+        this.onTransitionEnd= new Subject()
     }
 
-    startTransition(service: ServiceTheme, renderCurrentTheme: (currentServiceTheme: ServiceTheme) => void,
-                    continueCarousel: (currentServiceTheme: ServiceTheme) => void, delay:number = 0,
+    startTransition(service: ServiceTheme, delay:number = 0,
                     in_duration: number = 2500, active_duration: number = 4000, out_duration: number = 1500){
+        this.onTransitionStart.next()
         this.root.attr('opacity', 1)
 
         this.circles_path.attr('d', 'M' + this.canvas.width/2  + ' ' + this.canvas.height/2  + '  Q40 ' + ((this.canvas.height/2)+100) +' ,-10 -40')
@@ -193,7 +203,7 @@ export class Transition{
             .ease(Math.sqrt)
             .duration(in_duration - logo_delay)
             .on('end', () => {
-                renderCurrentTheme(service)
+                this.onTransitionMid.next()
             })
             .transition()
             .delay(active_duration)
@@ -203,7 +213,7 @@ export class Transition{
             .ease(easeExpOut)
             .duration(out_duration)
             .on('start', () => {
-                continueCarousel(service)
+                this.onTransitionEnd.next()
             })
     }
 
diff --git a/web/src/main.ts b/web/src/main.ts
index 79c3ed0..dc93133 100644
--- a/web/src/main.ts
+++ b/web/src/main.ts
@@ -41,7 +41,7 @@ function main(){
     // build canvas
     if (settings_data.map) {
         new GeoCanvas(theme, settings_data, newCircleSubject,
-            showWebsocketInfoboxSubject, updateVersionSubject, updateDatabaseInfoSubject, select(appContainer))
+            showWebsocketInfoboxSubject, updateVersionSubject, hatnoteVisServiceChangedSubject, updateDatabaseInfoSubject, select(appContainer))
     } else {
         new ListenToCanvas(theme, settings_data, newCircleSubject, newBannerSubject,
             showAudioInfoboxSubject, showWebsocketInfoboxSubject, updateVersionSubject, hatnoteVisServiceChangedSubject, updateDatabaseInfoSubject,select(appContainer))
-- 
GitLab