aboutsummaryrefslogtreecommitdiffhomepage
path: root/client/src/app
diff options
context:
space:
mode:
Diffstat (limited to 'client/src/app')
-rw-r--r--client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.html55
-rw-r--r--client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.scss6
-rw-r--r--client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.ts6
-rw-r--r--client/src/app/app.component.html10
-rw-r--r--client/src/app/app.component.scss38
-rw-r--r--client/src/app/app.component.ts71
-rw-r--r--client/src/app/core/server/server.service.ts17
7 files changed, 186 insertions, 17 deletions
diff --git a/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.html b/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.html
index 5703d5a2e..4ee573696 100644
--- a/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.html
+++ b/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.html
@@ -276,6 +276,58 @@
276 </div> 276 </div>
277 </div> 277 </div>
278 278
279 <div class="form-row mt-4"> <!-- broadcast grid -->
280 <div class="form-group col-12 col-lg-4 col-xl-3">
281 <div i18n class="inner-form-title">BROADCAST MESSAGE</div>
282 <div i18n class="inner-for-description">
283 Display a message on your instance
284 </div>
285 </div>
286
287 <div class="form-group form-group-right col-12 col-lg-8 col-xl-9">
288
289 <ng-container formGroupName="broadcastMessage">
290
291 <div class="form-group">
292 <my-peertube-checkbox
293 inputName="broadcastMessageEnabled" formControlName="enabled"
294 i18n-labelText labelText="Enable broadcast message"
295 ></my-peertube-checkbox>
296 </div>
297
298 <div class="form-group">
299 <my-peertube-checkbox
300 inputName="broadcastMessageDismissable" formControlName="dismissable"
301 i18n-labelText labelText="Allow users to dismiss the broadcast message "
302 ></my-peertube-checkbox>
303 </div>
304
305 <div class="form-group">
306 <label i18n for="broadcastMessageLevel">Broadcast message level</label>
307 <div class="peertube-select-container">
308 <select id="broadcastMessageLevel" formControlName="level" class="form-control">
309 <option value="info">info</option>
310 <option value="warning">warning</option>
311 <option value="error">error</option>
312 </select>
313 </div>
314 <div *ngIf="formErrors.broadcastMessage.level" class="form-error">{{ formErrors.broadcastMessage.level }}</div>
315 </div>
316
317 <div class="form-group">
318 <label i18n for="broadcastMessageMessage">Message</label><my-help helpType="markdownText"></my-help>
319 <my-markdown-textarea
320 name="broadcastMessageMessage" formControlName="message" textareaMaxWidth="500px"
321 [classes]="{ 'input-error': formErrors['broadcastMessage.message'] }"
322 ></my-markdown-textarea>
323 <div *ngIf="formErrors.broadcastMessage.message" class="form-error">{{ formErrors.broadcastMessage.message }}</div>
324 </div>
325
326 </ng-container>
327
328 </div>
329 </div>
330
279 <div class="form-row mt-4"> <!-- new users grid --> 331 <div class="form-row mt-4"> <!-- new users grid -->
280 <div class="form-group col-12 col-lg-4 col-xl-3"> 332 <div class="form-group col-12 col-lg-4 col-xl-3">
281 <div i18n class="inner-form-title">NEW USERS</div> 333 <div i18n class="inner-form-title">NEW USERS</div>
@@ -801,8 +853,9 @@
801 <div class="form-row mt-4"> <!-- submit placement block --> 853 <div class="form-row mt-4"> <!-- submit placement block -->
802 <div class="col-md-7 col-xl-5"></div> 854 <div class="col-md-7 col-xl-5"></div>
803 <div class="col-md-5 col-xl-5"> 855 <div class="col-md-5 col-xl-5">
856 <span class="form-error submit-error" i18n *ngIf="!form.valid">It seems like the configuration is invalid. Please search for potential errors in the different tabs.</span>
857
804 <input (click)="formValidated()" type="submit" i18n-value value="Update configuration" [disabled]="!form.valid"> 858 <input (click)="formValidated()" type="submit" i18n-value value="Update configuration" [disabled]="!form.valid">
805 <span class="form-error" i18n *ngIf="!form.valid">It seems like the configuration is invalid. Please search for potential errors in the different tabs.</span>
806 </div> 859 </div>
807 </div> 860 </div>
808</form> 861</form>
diff --git a/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.scss b/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.scss
index 9ee960ad6..2bfa92da4 100644
--- a/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.scss
+++ b/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.scss
@@ -76,4 +76,8 @@ ngb-tabset:not(.previews) ::ng-deep {
76 .nav-link { 76 .nav-link {
77 font-size: 105%; 77 font-size: 105%;
78 } 78 }
79} \ No newline at end of file 79}
80
81.submit-error {
82 margin-bottom: 20px;
83}
diff --git a/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.ts b/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.ts
index cea314cea..6d59494c8 100644
--- a/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.ts
+++ b/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.ts
@@ -215,6 +215,12 @@ export class EditCustomConfigComponent extends FormReactive implements OnInit, A
215 indexUrl: this.customConfigValidatorsService.INDEX_URL 215 indexUrl: this.customConfigValidatorsService.INDEX_URL
216 } 216 }
217 } 217 }
218 },
219 broadcastMessage: {
220 enabled: null,
221 level: null,
222 dismissable: null,
223 message: null
218 } 224 }
219 } 225 }
220 226
diff --git a/client/src/app/app.component.html b/client/src/app/app.component.html
index b0d2e5050..b243c129b 100644
--- a/client/src/app/app.component.html
+++ b/client/src/app/app.component.html
@@ -25,6 +25,16 @@
25 <div id="content" tabindex="-1" class="main-col" [ngClass]="{ expanded: menu.isMenuDisplayed === false }"> 25 <div id="content" tabindex="-1" class="main-col" [ngClass]="{ expanded: menu.isMenuDisplayed === false }">
26 26
27 <div class="main-row"> 27 <div class="main-row">
28
29 <div *ngIf="broadcastMessage" class="broadcast-message alert" [ngClass]="broadcastMessage.class">
30 <div [innerHTML]="broadcastMessage.message"></div>
31
32 <my-global-icon
33 *ngIf="broadcastMessage.dismissable" (click)="hideBroadcastMessage()"
34 iconName="cross" role="button" title="Close this message" i18n-title
35 ></my-global-icon>
36 </div>
37
28 <router-outlet></router-outlet> 38 <router-outlet></router-outlet>
29 </div> 39 </div>
30 </div> 40 </div>
diff --git a/client/src/app/app.component.scss b/client/src/app/app.component.scss
index 0c33dc4a1..27fd69c8d 100644
--- a/client/src/app/app.component.scss
+++ b/client/src/app/app.component.scss
@@ -1,5 +1,7 @@
1@import '_variables'; 1@import '_variables';
2@import '_mixins'; 2@import '_mixins';
3@import '~bootstrap/scss/functions';
4@import '~bootstrap/scss/variables';
3 5
4.peertube-container { 6.peertube-container {
5 padding-bottom: 20px; 7 padding-bottom: 20px;
@@ -88,3 +90,39 @@
88 flex: 1; 90 flex: 1;
89 } 91 }
90} 92}
93
94.broadcast-message {
95 min-height: 50px;
96 text-align: center;
97 margin-bottom: 0;
98 border-radius: 0;
99 display: grid;
100 grid-template-columns: 1fr 30px;
101 column-gap: 10px;
102
103 my-global-icon {
104 justify-self: center;
105 align-self: center;
106 cursor: pointer;
107
108 width: 20px;
109 }
110
111 @each $color, $value in $theme-colors {
112 &.alert-#{$color} {
113 my-global-icon {
114 @include apply-svg-color(theme-color-level($color, $alert-color-level));
115 }
116 }
117 }
118
119 ::ng-deep {
120 p {
121 font-size: 16px;
122 }
123
124 p:last-child {
125 margin-bottom: 0;
126 }
127 }
128}
diff --git a/client/src/app/app.component.ts b/client/src/app/app.component.ts
index 12c0efd8a..a464e90fa 100644
--- a/client/src/app/app.component.ts
+++ b/client/src/app/app.component.ts
@@ -4,7 +4,7 @@ import { Event, GuardsCheckStart, NavigationEnd, Router, Scroll } from '@angular
4import { AuthService, RedirectService, ServerService, ThemeService } from '@app/core' 4import { AuthService, RedirectService, ServerService, ThemeService } from '@app/core'
5import { is18nPath } from '../../../shared/models/i18n' 5import { is18nPath } from '../../../shared/models/i18n'
6import { ScreenService } from '@app/shared/misc/screen.service' 6import { ScreenService } from '@app/shared/misc/screen.service'
7import { filter, map, pairwise } from 'rxjs/operators' 7import { filter, map, pairwise, first } from 'rxjs/operators'
8import { Hotkey, HotkeysService } from 'angular2-hotkeys' 8import { Hotkey, HotkeysService } from 'angular2-hotkeys'
9import { I18n } from '@ngx-translate/i18n-polyfill' 9import { I18n } from '@ngx-translate/i18n-polyfill'
10import { PlatformLocation, ViewportScroller } from '@angular/common' 10import { PlatformLocation, ViewportScroller } from '@angular/common'
@@ -19,6 +19,10 @@ import { ServerConfig, UserRole } from '@shared/models'
19import { User } from '@app/shared' 19import { User } from '@app/shared'
20import { InstanceService } from '@app/shared/instance/instance.service' 20import { InstanceService } from '@app/shared/instance/instance.service'
21import { MenuService } from './core/menu/menu.service' 21import { MenuService } from './core/menu/menu.service'
22import { BroadcastMessageLevel } from '@shared/models/server'
23import { MarkdownService } from './shared/renderer'
24import { concat } from 'rxjs'
25import { peertubeLocalStorage } from './shared/misc/peertube-web-storage'
22 26
23@Component({ 27@Component({
24 selector: 'my-app', 28 selector: 'my-app',
@@ -26,11 +30,14 @@ import { MenuService } from './core/menu/menu.service'
26 styleUrls: [ './app.component.scss' ] 30 styleUrls: [ './app.component.scss' ]
27}) 31})
28export class AppComponent implements OnInit, AfterViewInit { 32export class AppComponent implements OnInit, AfterViewInit {
33 private static BROADCAST_MESSAGE_KEY = 'app-broadcast-message-dismissed'
34
29 @ViewChild('welcomeModal') welcomeModal: WelcomeModalComponent 35 @ViewChild('welcomeModal') welcomeModal: WelcomeModalComponent
30 @ViewChild('instanceConfigWarningModal') instanceConfigWarningModal: InstanceConfigWarningModalComponent 36 @ViewChild('instanceConfigWarningModal') instanceConfigWarningModal: InstanceConfigWarningModalComponent
31 @ViewChild('customModal') customModal: CustomModalComponent 37 @ViewChild('customModal') customModal: CustomModalComponent
32 38
33 customCSS: SafeHtml 39 customCSS: SafeHtml
40 broadcastMessage: { message: string, dismissable: boolean, class: string } | null = null
34 41
35 private serverConfig: ServerConfig 42 private serverConfig: ServerConfig
36 43
@@ -50,6 +57,7 @@ export class AppComponent implements OnInit, AfterViewInit {
50 private hooks: HooksService, 57 private hooks: HooksService,
51 private location: PlatformLocation, 58 private location: PlatformLocation,
52 private modalService: NgbModal, 59 private modalService: NgbModal,
60 private markdownService: MarkdownService,
53 public menu: MenuService 61 public menu: MenuService
54 ) { } 62 ) { }
55 63
@@ -81,6 +89,7 @@ export class AppComponent implements OnInit, AfterViewInit {
81 this.initRouteEvents() 89 this.initRouteEvents()
82 this.injectJS() 90 this.injectJS()
83 this.injectCSS() 91 this.injectCSS()
92 this.injectBroadcastMessage()
84 93
85 this.initHotkeys() 94 this.initHotkeys()
86 95
@@ -97,6 +106,12 @@ export class AppComponent implements OnInit, AfterViewInit {
97 return this.authService.isLoggedIn() 106 return this.authService.isLoggedIn()
98 } 107 }
99 108
109 hideBroadcastMessage () {
110 peertubeLocalStorage.setItem(AppComponent.BROADCAST_MESSAGE_KEY, this.serverConfig.broadcastMessage.message)
111
112 this.broadcastMessage = null
113 }
114
100 private initRouteEvents () { 115 private initRouteEvents () {
101 let resetScroll = true 116 let resetScroll = true
102 const eventsObs = this.router.events 117 const eventsObs = this.router.events
@@ -165,6 +180,36 @@ export class AppComponent implements OnInit, AfterViewInit {
165 ).subscribe(() => this.menu.isMenuDisplayed = false) // User clicked on a link in the menu, change the page 180 ).subscribe(() => this.menu.isMenuDisplayed = false) // User clicked on a link in the menu, change the page
166 } 181 }
167 182
183 private injectBroadcastMessage () {
184 concat(
185 this.serverService.getConfig().pipe(first()),
186 this.serverService.configReloaded
187 ).subscribe(async config => {
188 this.broadcastMessage = null
189
190 const messageConfig = config.broadcastMessage
191
192 if (messageConfig.enabled) {
193 // Already dismissed this message?
194 if (messageConfig.dismissable && localStorage.getItem(AppComponent.BROADCAST_MESSAGE_KEY) === messageConfig.message) {
195 return
196 }
197
198 const classes: { [id in BroadcastMessageLevel]: string } = {
199 info: 'alert-info',
200 warning: 'alert-warning',
201 error: 'alert-danger'
202 }
203
204 this.broadcastMessage = {
205 message: await this.markdownService.completeMarkdownToHTML(messageConfig.message),
206 dismissable: messageConfig.dismissable,
207 class: classes[messageConfig.level]
208 }
209 }
210 })
211 }
212
168 private injectJS () { 213 private injectJS () {
169 // Inject JS 214 // Inject JS
170 this.serverService.getConfig() 215 this.serverService.getConfig()
@@ -182,17 +227,19 @@ export class AppComponent implements OnInit, AfterViewInit {
182 227
183 private injectCSS () { 228 private injectCSS () {
184 // Inject CSS if modified (admin config settings) 229 // Inject CSS if modified (admin config settings)
185 this.serverService.configReloaded 230 concat(
186 .subscribe(() => { 231 this.serverService.getConfig().pipe(first()),
187 const headStyle = document.querySelector('style.custom-css-style') 232 this.serverService.configReloaded
188 if (headStyle) headStyle.parentNode.removeChild(headStyle) 233 ).subscribe(config => {
189 234 const headStyle = document.querySelector('style.custom-css-style')
190 // We test customCSS if the admin removed the css 235 if (headStyle) headStyle.parentNode.removeChild(headStyle)
191 if (this.customCSS || this.serverConfig.instance.customizations.css) { 236
192 const styleTag = '<style>' + this.serverConfig.instance.customizations.css + '</style>' 237 // We test customCSS if the admin removed the css
193 this.customCSS = this.domSanitizer.bypassSecurityTrustHtml(styleTag) 238 if (this.customCSS || config.instance.customizations.css) {
194 } 239 const styleTag = '<style>' + config.instance.customizations.css + '</style>'
195 }) 240 this.customCSS = this.domSanitizer.bypassSecurityTrustHtml(styleTag)
241 }
242 })
196 } 243 }
197 244
198 private async loadPlugins () { 245 private async loadPlugins () {
diff --git a/client/src/app/core/server/server.service.ts b/client/src/app/core/server/server.service.ts
index eac8f85e4..fdfbe4c02 100644
--- a/client/src/app/core/server/server.service.ts
+++ b/client/src/app/core/server/server.service.ts
@@ -21,7 +21,7 @@ export class ServerService {
21 21
22 private static CONFIG_LOCAL_STORAGE_KEY = 'server-config' 22 private static CONFIG_LOCAL_STORAGE_KEY = 'server-config'
23 23
24 configReloaded = new Subject<void>() 24 configReloaded = new Subject<ServerConfig>()
25 25
26 private localeObservable: Observable<any> 26 private localeObservable: Observable<any>
27 private videoLicensesObservable: Observable<VideoConstant<number>[]> 27 private videoLicensesObservable: Observable<VideoConstant<number>[]>
@@ -139,6 +139,12 @@ export class ServerService {
139 indexUrl: 'https://instances.joinpeertube.org' 139 indexUrl: 'https://instances.joinpeertube.org'
140 } 140 }
141 } 141 }
142 },
143 broadcastMessage: {
144 enabled: false,
145 message: '',
146 level: 'info',
147 dismissable: false
142 } 148 }
143 } 149 }
144 150
@@ -162,6 +168,11 @@ export class ServerService {
162 resetConfig () { 168 resetConfig () {
163 this.configLoaded = false 169 this.configLoaded = false
164 this.configReset = true 170 this.configReset = true
171
172 // Notify config update
173 this.getConfig().subscribe(() => {
174 // empty, to fire a reset config event
175 })
165 } 176 }
166 177
167 getConfig () { 178 getConfig () {
@@ -175,9 +186,9 @@ export class ServerService {
175 this.config = config 186 this.config = config
176 this.configLoaded = true 187 this.configLoaded = true
177 }), 188 }),
178 tap(() => { 189 tap(config => {
179 if (this.configReset) { 190 if (this.configReset) {
180 this.configReloaded.next() 191 this.configReloaded.next(config)
181 this.configReset = false 192 this.configReset = false
182 } 193 }
183 }), 194 }),