Add applause in client
Applause button in Jitsi Bar, Add Applause Service, Add Applause Display component, Add Config varriables, Integrate applause display component in Jitsi bar, Integrate custom vertical progress bar as own component, - vertical and more customizable than the mat-progress-bar - includes an optional end icon - animated and themed Add custom clapping icon applause particles using tsparticles custom particle component dynamic add and remove functions to alter particles in runtime Set own particle shape Use smooth emitter for clean particle spawning
This commit is contained in:
parent
97950d5baa
commit
e3d718cad0
2437
client/package-lock.json
generated
2437
client/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -66,6 +66,7 @@
|
||||
"lz4js": "^0.2.0",
|
||||
"material-icon-font": "git+https://github.com/petergng/materialIconFont.git",
|
||||
"moment": "^2.27.0",
|
||||
"ng-particles": "^2.1.11",
|
||||
"ng2-charts": "^2.4.0",
|
||||
"ng2-pdf-viewer": "6.3.2",
|
||||
"ngx-device-detector": "^2.0.0",
|
||||
@ -78,6 +79,7 @@
|
||||
"rxjs": "^6.6.2",
|
||||
"tinymce": "5.4.2",
|
||||
"tslib": "^1.10.0",
|
||||
"tsparticles": "^1.18.11",
|
||||
"video.js": "^7.8.4",
|
||||
"zone.js": "~0.10.2"
|
||||
},
|
||||
|
@ -1,4 +1,6 @@
|
||||
import { ApplicationRef, Component } from '@angular/core';
|
||||
import { MatIconRegistry } from '@angular/material/icon';
|
||||
import { DomSanitizer } from '@angular/platform-browser';
|
||||
import { Router } from '@angular/router';
|
||||
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
@ -70,6 +72,8 @@ export class AppComponent {
|
||||
* @param dataStoreUpgradeService
|
||||
*/
|
||||
public constructor(
|
||||
private matIconRegistry: MatIconRegistry,
|
||||
private domSanitizer: DomSanitizer,
|
||||
translate: TranslateService,
|
||||
appRef: ApplicationRef,
|
||||
servertimeService: ServertimeService,
|
||||
@ -100,6 +104,7 @@ export class AppComponent {
|
||||
this.overloadArrayFunctions();
|
||||
this.overloadSetFunctions();
|
||||
this.overloadModulo();
|
||||
this.loadCustomIcons();
|
||||
|
||||
// Wait until the App reaches a stable state.
|
||||
// Required for the Service Worker.
|
||||
@ -204,4 +209,11 @@ export class AppComponent {
|
||||
enumerable: false
|
||||
});
|
||||
}
|
||||
|
||||
private loadCustomIcons(): void {
|
||||
this.matIconRegistry.addSvgIcon(
|
||||
`clapping_hands`,
|
||||
this.domSanitizer.bypassSecurityTrustResourceUrl('../assets/svg/clapping_hands.svg')
|
||||
);
|
||||
}
|
||||
}
|
||||
|
20
client/src/app/core/ui-services/applause.service.spec.ts
Normal file
20
client/src/app/core/ui-services/applause.service.spec.ts
Normal file
@ -0,0 +1,20 @@
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
|
||||
import { E2EImportsModule } from 'e2e-imports.module';
|
||||
|
||||
import { ApplauseService } from './applause.service';
|
||||
|
||||
describe('ApplauseService', () => {
|
||||
let service: ApplauseService;
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [E2EImportsModule]
|
||||
});
|
||||
service = TestBed.inject(ApplauseService);
|
||||
});
|
||||
|
||||
it('should be created', () => {
|
||||
expect(service).toBeTruthy();
|
||||
});
|
||||
});
|
84
client/src/app/core/ui-services/applause.service.ts
Normal file
84
client/src/app/core/ui-services/applause.service.ts
Normal file
@ -0,0 +1,84 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
|
||||
import { Observable, Subject } from 'rxjs';
|
||||
import { distinctUntilChanged, filter, map } from 'rxjs/operators';
|
||||
|
||||
import { ConfigService } from './config.service';
|
||||
import { HttpService } from '../core-services/http.service';
|
||||
import { NotifyService } from '../core-services/notify.service';
|
||||
|
||||
export interface Applause {
|
||||
level: number;
|
||||
presentUsers: number;
|
||||
}
|
||||
|
||||
export enum ApplauseType {
|
||||
particles = 'applause-type-particles',
|
||||
bar = 'applause-type-bar'
|
||||
}
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class ApplauseService {
|
||||
private applausePath = '/system/applause';
|
||||
private applauseNotifyPath = 'applause';
|
||||
private minApplauseLevel: number;
|
||||
private maxApplauseLevel: number;
|
||||
private presentApplauseUsers: number;
|
||||
|
||||
public applauseType: ApplauseType;
|
||||
|
||||
private applauseLevelSubject: Subject<number> = new Subject<number>();
|
||||
public applauseLevelObservable = this.applauseLevelSubject.asObservable();
|
||||
|
||||
private get maxApplause(): number {
|
||||
return this.maxApplauseLevel || this.presentApplauseUsers || 0;
|
||||
}
|
||||
|
||||
public constructor(
|
||||
configService: ConfigService,
|
||||
private httpService: HttpService,
|
||||
private notifyService: NotifyService
|
||||
) {
|
||||
configService.get<number>('general_system_applause_min_amount').subscribe(minLevel => {
|
||||
this.minApplauseLevel = minLevel;
|
||||
});
|
||||
configService.get<number>('general_system_applause_max_amount').subscribe(maxLevel => {
|
||||
this.maxApplauseLevel = maxLevel;
|
||||
});
|
||||
configService.get<ApplauseType>('general_system_applause_type').subscribe((type: ApplauseType) => {
|
||||
this.applauseType = type;
|
||||
});
|
||||
this.notifyService
|
||||
.getMessageObservable<Applause>(this.applauseNotifyPath)
|
||||
.pipe(
|
||||
map(notify => notify.message as Applause),
|
||||
/**
|
||||
* only updates when the effective applause level changes
|
||||
*/
|
||||
distinctUntilChanged((prev, curr) => {
|
||||
return prev.level === curr.level;
|
||||
}),
|
||||
filter(curr => {
|
||||
return curr.level === 0 || curr.level >= this.minApplauseLevel;
|
||||
})
|
||||
)
|
||||
.subscribe(applause => {
|
||||
this.presentApplauseUsers = applause.presentUsers;
|
||||
this.applauseLevelSubject.next(applause.level);
|
||||
});
|
||||
}
|
||||
|
||||
public async sendApplause(): Promise<void> {
|
||||
await this.httpService.post(this.applausePath);
|
||||
}
|
||||
|
||||
public getApplauseQuote(applauseLevel: number): number {
|
||||
if (!applauseLevel) {
|
||||
return 0;
|
||||
}
|
||||
const quote = applauseLevel / this.maxApplause || 0;
|
||||
return quote > 1 ? 1 : quote;
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
import { animate, style, transition, trigger } from '@angular/animations';
|
||||
import { animate, state, style, transition, trigger } from '@angular/animations';
|
||||
|
||||
const slideIn = [style({ transform: 'translateX(-85%)' }), animate('600ms ease')];
|
||||
const slideOut = [
|
||||
@ -11,4 +11,9 @@ const slideOut = [
|
||||
)
|
||||
];
|
||||
|
||||
export const fadeAnimation = trigger('fade', [
|
||||
state('in', style({ opacity: 1 })),
|
||||
transition(':enter', [style({ opacity: 0 }), animate(600)]),
|
||||
transition(':leave', animate(600, style({ opacity: 0 })))
|
||||
]);
|
||||
export const navItemAnim = trigger('navItemAnim', [transition(':enter', slideIn), transition(':leave', slideOut)]);
|
||||
|
@ -0,0 +1,9 @@
|
||||
<div class="bar-wrapper" *ngIf="isApplauseTypeBar">
|
||||
<os-progress class="progress-bar" [value]="percent">
|
||||
<div class="level-indicator">
|
||||
<div class="level">
|
||||
<b *ngIf="showLevel && hasLevel" [@fade]="'in'">{{ level }}</b>
|
||||
</div>
|
||||
</div>
|
||||
</os-progress>
|
||||
</div>
|
@ -0,0 +1,26 @@
|
||||
.bar-wrapper {
|
||||
min-width: 30px;
|
||||
height: 100%;
|
||||
|
||||
.progress-bar {
|
||||
display: block;
|
||||
width: 30px;
|
||||
height: 100%;
|
||||
margin-left: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.level-indicator {
|
||||
height: 50px;
|
||||
display: block;
|
||||
text-align: center;
|
||||
|
||||
.level {
|
||||
display: inline-block;
|
||||
margin-top: 25px;
|
||||
}
|
||||
}
|
||||
|
||||
.particle-display {
|
||||
// height: 100%;
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { E2EImportsModule } from 'e2e-imports.module';
|
||||
|
||||
import { ApplauseDisplayComponent } from './applause-display.component';
|
||||
|
||||
describe('ApplauseDisplayComponent', () => {
|
||||
let component: ApplauseDisplayComponent;
|
||||
let fixture: ComponentFixture<ApplauseDisplayComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [E2EImportsModule]
|
||||
}).compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(ApplauseDisplayComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
@ -0,0 +1,56 @@
|
||||
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, OnInit, ViewEncapsulation } from '@angular/core';
|
||||
import { MatSnackBar } from '@angular/material/snack-bar';
|
||||
import { Title } from '@angular/platform-browser';
|
||||
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
|
||||
import { Applause, ApplauseService, ApplauseType } from 'app/core/ui-services/applause.service';
|
||||
import { ConfigService } from 'app/core/ui-services/config.service';
|
||||
import { fadeAnimation } from 'app/shared/animations';
|
||||
import { BaseViewComponentDirective } from 'app/site/base/base-view';
|
||||
|
||||
@Component({
|
||||
selector: 'os-applause-display',
|
||||
templateUrl: './applause-display.component.html',
|
||||
styleUrls: ['./applause-display.component.scss'],
|
||||
animations: [fadeAnimation],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
encapsulation: ViewEncapsulation.None
|
||||
})
|
||||
export class ApplauseDisplayComponent extends BaseViewComponentDirective {
|
||||
public level = 0;
|
||||
public showLevel: boolean;
|
||||
public percent = 0;
|
||||
|
||||
public get hasLevel(): boolean {
|
||||
return !!this.level;
|
||||
}
|
||||
|
||||
public get isApplauseTypeBar(): boolean {
|
||||
return this.applauseService.applauseType === ApplauseType.bar;
|
||||
}
|
||||
|
||||
public constructor(
|
||||
title: Title,
|
||||
translate: TranslateService,
|
||||
matSnackBar: MatSnackBar,
|
||||
cd: ChangeDetectorRef,
|
||||
private applauseService: ApplauseService,
|
||||
configService: ConfigService
|
||||
) {
|
||||
super(title, translate, matSnackBar);
|
||||
this.subscriptions.push(
|
||||
applauseService.applauseLevelObservable.subscribe(applauseLevel => {
|
||||
this.level = applauseLevel || 0;
|
||||
this.percent = this.applauseService.getApplauseQuote(this.level) * 100;
|
||||
cd.markForCheck();
|
||||
}),
|
||||
configService.get<ApplauseType>('general_system_applause_type').subscribe(() => {
|
||||
cd.markForCheck();
|
||||
}),
|
||||
configService.get<boolean>('general_system_applause_show_level').subscribe(show => {
|
||||
this.showLevel = show;
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
@ -61,9 +61,7 @@
|
||||
matTooltip="{{ 'Exit live conference and continue livestream' | translate }}"
|
||||
*ngIf="videoStreamUrl && canSeeLiveStream && !isJitsiDialogOpen"
|
||||
>
|
||||
<mat-icon color="warn">
|
||||
meeting_room
|
||||
</mat-icon>
|
||||
<mat-icon color="warn"> meeting_room </mat-icon>
|
||||
</button>
|
||||
|
||||
<!-- mute/unmute button -->
|
||||
@ -90,7 +88,11 @@
|
||||
(click)="enterConversation()"
|
||||
matTooltip="{{ 'Enter live conference' | translate }}"
|
||||
>
|
||||
<mat-icon color="primary" [@fadeInOut]="isEnterMeetingRoomVisible" (@fadeInOut.done)="triggerMeetingRoomButtonAnimation()">
|
||||
<mat-icon
|
||||
color="primary"
|
||||
[@fadeInOut]="isEnterMeetingRoomVisible"
|
||||
(@fadeInOut.done)="triggerMeetingRoomButtonAnimation()"
|
||||
>
|
||||
meeting_room
|
||||
</mat-icon>
|
||||
</button>
|
||||
@ -103,11 +105,23 @@
|
||||
*ngIf="enableJitsi && !isAccessPermitted"
|
||||
[routerLink]="['/agenda/speakers']"
|
||||
>
|
||||
<mat-icon>
|
||||
no_meeting_room
|
||||
</mat-icon>
|
||||
<mat-icon> no_meeting_room </mat-icon>
|
||||
</a>
|
||||
</ng-container>
|
||||
|
||||
<!-- applause button -->
|
||||
<button
|
||||
class="quick-icon indicator"
|
||||
[disabled]="applauseDisabled"
|
||||
mat-mini-fab
|
||||
(click)="sendApplause()"
|
||||
matTooltip="{{ 'Send applause' | translate }}"
|
||||
*ngIf="showApplause"
|
||||
[matBadge]="showApplauseBadge ? applauseLevel : null"
|
||||
matBadgeColor="accent"
|
||||
>
|
||||
<mat-icon svgIcon="clapping_hands"></mat-icon>
|
||||
</button>
|
||||
</span>
|
||||
|
||||
<span
|
||||
@ -118,6 +132,8 @@
|
||||
'cast-shadow': showJitsiWindow
|
||||
}"
|
||||
>
|
||||
<os-applause-display *ngIf="showApplause && isApplauseTypeBar" class="applause"></os-applause-display>
|
||||
|
||||
<!-- open-window button -->
|
||||
<button class="toggle-list-button" mat-button (click)="toggleShowJitsi()">
|
||||
<ng-container *ngIf="currentState == state.jitsi">
|
||||
@ -149,7 +165,7 @@
|
||||
<div
|
||||
class="jitsi-list"
|
||||
[ngClass]="{
|
||||
'cdk-visually-hidden': !showJitsiWindow
|
||||
'hide-height': !showJitsiWindow
|
||||
}"
|
||||
>
|
||||
<ng-container *ngIf="currentState == state.jitsi">
|
||||
@ -175,6 +191,10 @@
|
||||
|
||||
<!-- user list -->
|
||||
<div class="room-members" *ngIf="isJitsiActive && isJoined">
|
||||
<os-particle-display
|
||||
*ngIf="isApplauseTypeParticles"
|
||||
class="room-list-applause-particles"
|
||||
></os-particle-display>
|
||||
<div class="member-list">
|
||||
<ol>
|
||||
<li
|
||||
@ -196,6 +216,7 @@
|
||||
<ng-container *ngIf="currentState == state.stream">
|
||||
<os-vjs-player
|
||||
[videoUrl]="videoStreamUrl"
|
||||
[showParticles]="isApplauseTypeParticles"
|
||||
(started)="onSteamStarted()"
|
||||
*ngIf="(canSeeLiveStream && !streamActiveInAnotherTab) || streamRunning"
|
||||
></os-vjs-player>
|
||||
|
@ -56,6 +56,7 @@
|
||||
|
||||
.jitsi-bar {
|
||||
display: flex;
|
||||
position: relative;
|
||||
justify-content: flex-end;
|
||||
$wrapper-padding: 5px;
|
||||
$bar-height: 40px;
|
||||
@ -90,11 +91,20 @@
|
||||
}
|
||||
|
||||
.list-wrapper {
|
||||
position: relative;
|
||||
pointer-events: all;
|
||||
min-height: $bar-height;
|
||||
padding-top: $wrapper-padding;
|
||||
border-top-right-radius: 4px;
|
||||
|
||||
.applause {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
width: 90px;
|
||||
left: -90px;
|
||||
bottom: 50px;
|
||||
}
|
||||
|
||||
.toggle-list-button {
|
||||
position: relative;
|
||||
line-height: normal;
|
||||
@ -121,6 +131,7 @@
|
||||
.jitsi-list {
|
||||
.content {
|
||||
height: 40vh;
|
||||
max-height: 100%;
|
||||
clear: both;
|
||||
|
||||
.disconnected {
|
||||
@ -136,7 +147,15 @@
|
||||
}
|
||||
|
||||
.room-members {
|
||||
height: inherit;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
|
||||
.room-list-applause-particles {
|
||||
position: absolute;
|
||||
height: 100%;
|
||||
width: 70px;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
.member-list {
|
||||
max-height: 100%;
|
||||
|
@ -17,6 +17,10 @@
|
||||
|
||||
.indicator {
|
||||
color: mat-color($primary, default-contrast);
|
||||
|
||||
svg path {
|
||||
fill: mat-color($primary) !important;
|
||||
}
|
||||
}
|
||||
|
||||
.toggle-list-button {
|
||||
|
@ -11,6 +11,7 @@ import { ConstantsService } from 'app/core/core-services/constants.service';
|
||||
import { OperatorService } from 'app/core/core-services/operator.service';
|
||||
import { Deferred } from 'app/core/promises/deferred';
|
||||
import { UserRepositoryService } from 'app/core/repositories/users/user-repository.service';
|
||||
import { ApplauseService, ApplauseType } from 'app/core/ui-services/applause.service';
|
||||
import { ConfigService } from 'app/core/ui-services/config.service';
|
||||
import { UserMediaPermService } from 'app/core/ui-services/user-media-perm.service';
|
||||
import { UserListIndexType } from 'app/site/agenda/models/view-list-of-speakers';
|
||||
@ -96,6 +97,10 @@ export class JitsiComponent extends BaseViewComponentDirective implements OnInit
|
||||
public showJitsiWindow = true;
|
||||
public muted = true;
|
||||
|
||||
public showApplause: boolean;
|
||||
public applauseDisabled = false;
|
||||
private applauseTimeout: number;
|
||||
|
||||
@ViewChild('jitsi')
|
||||
private jitsiNode: ElementRef;
|
||||
|
||||
@ -180,6 +185,26 @@ export class JitsiComponent extends BaseViewComponentDirective implements OnInit
|
||||
public currentState: ConferenceState;
|
||||
public isEnterMeetingRoomVisible = true;
|
||||
|
||||
public applauseLevel = 0;
|
||||
private showApplauseLevel: boolean;
|
||||
private isApplausBarUsed: boolean;
|
||||
|
||||
public get showApplauseBadge(): boolean {
|
||||
return this.showApplauseLevel && this.applauseLevel > 0 && (!this.showJitsiWindow || !this.isApplausBarUsed);
|
||||
}
|
||||
|
||||
private get applauseType(): ApplauseType {
|
||||
return this.applauseService.applauseType;
|
||||
}
|
||||
|
||||
public get isApplauseTypeBar(): boolean {
|
||||
return this.applauseType === ApplauseType.bar;
|
||||
}
|
||||
|
||||
public get isApplauseTypeParticles(): boolean {
|
||||
return this.applauseType === ApplauseType.particles;
|
||||
}
|
||||
|
||||
private configOverwrite = {
|
||||
startAudioOnly: false,
|
||||
// allows jitsi on mobile devices
|
||||
@ -237,7 +262,8 @@ export class JitsiComponent extends BaseViewComponentDirective implements OnInit
|
||||
private constantsService: ConstantsService,
|
||||
private configService: ConfigService,
|
||||
private closService: CurrentListOfSpeakersService,
|
||||
private userMediaPermService: UserMediaPermService
|
||||
private userMediaPermService: UserMediaPermService,
|
||||
private applauseService: ApplauseService
|
||||
) {
|
||||
super(titleService, translate, snackBar);
|
||||
}
|
||||
@ -350,6 +376,22 @@ export class JitsiComponent extends BaseViewComponentDirective implements OnInit
|
||||
}),
|
||||
this.configService.get<boolean>('general_system_conference_open_video').subscribe(open => {
|
||||
this.configOverwrite.startWithVideoMuted = !open;
|
||||
}),
|
||||
this.configService.get<boolean>('general_system_applause_enable').subscribe(enable => {
|
||||
this.showApplause = enable;
|
||||
}),
|
||||
this.configService.get<number>('general_system_stream_applause_timeout').subscribe(timeout => {
|
||||
this.applauseTimeout = (timeout || 1) * 1000;
|
||||
}),
|
||||
this.configService.get<boolean>('general_system_applause_show_level').subscribe(show => {
|
||||
this.showApplauseLevel = show;
|
||||
}),
|
||||
this.configService.get<any>('general_system_applause_type').subscribe(type => {
|
||||
if (type === 'applause-type-bar') {
|
||||
this.isApplausBarUsed = true;
|
||||
} else {
|
||||
this.isApplausBarUsed = false;
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
@ -362,7 +404,10 @@ export class JitsiComponent extends BaseViewComponentDirective implements OnInit
|
||||
map(los => los?.findUserIndexOnList(this.operator.user.id) ?? -1),
|
||||
distinctUntilChanged()
|
||||
)
|
||||
.subscribe(userLosIndex => this.autoJoinJitsiByLosIndex(userLosIndex))
|
||||
.subscribe(userLosIndex => this.autoJoinJitsiByLosIndex(userLosIndex)),
|
||||
this.applauseService.applauseLevelObservable.subscribe(applauseLevel => {
|
||||
this.applauseLevel = applauseLevel || 0;
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
@ -595,4 +640,12 @@ export class JitsiComponent extends BaseViewComponentDirective implements OnInit
|
||||
private setConferenceState(newState: ConferenceState): void {
|
||||
this.currentState = newState;
|
||||
}
|
||||
|
||||
public sendApplause(): void {
|
||||
this.applauseDisabled = true;
|
||||
this.applauseService.sendApplause();
|
||||
setTimeout(() => {
|
||||
this.applauseDisabled = false;
|
||||
}, this.applauseTimeout);
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,3 @@
|
||||
<div [osResized]="resizeSubject" class="particle-wrapper">
|
||||
<Particles id="particles" [options]="particlesOptions" (particlesLoaded)="particlesLoaded($event)"> </Particles>
|
||||
</div>
|
@ -0,0 +1,7 @@
|
||||
.particle-wrapper {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.tsparticles-canvas-el {
|
||||
pointer-events: none !important;
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { E2EImportsModule } from 'e2e-imports.module';
|
||||
|
||||
import { ParticleDisplayComponent } from './particle-display.component';
|
||||
|
||||
describe('ParticleDisplayComponent', () => {
|
||||
let component: ParticleDisplayComponent;
|
||||
let fixture: ComponentFixture<ParticleDisplayComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [E2EImportsModule]
|
||||
}).compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(ParticleDisplayComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
@ -0,0 +1,233 @@
|
||||
import { Component, Input, OnInit, ViewEncapsulation } from '@angular/core';
|
||||
import { MatSnackBar } from '@angular/material/snack-bar';
|
||||
import { Title } from '@angular/platform-browser';
|
||||
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { Subject } from 'rxjs';
|
||||
import { auditTime } from 'rxjs/operators';
|
||||
import { Container, SizeMode } from 'tsparticles';
|
||||
import { Shape } from 'tsparticles/dist/Options/Classes/Particles/Shape/Shape';
|
||||
import { IImageShape } from 'tsparticles/dist/Options/Interfaces/Particles/Shape/IImageShape';
|
||||
import { Emitters } from 'tsparticles/dist/Plugins/Emitters/Emitters';
|
||||
|
||||
import { ApplauseService } from 'app/core/ui-services/applause.service';
|
||||
import { ConfigService } from 'app/core/ui-services/config.service';
|
||||
import { ElementSize } from 'app/shared/directives/resized.directive';
|
||||
import { BaseViewComponentDirective } from 'app/site/base/base-view';
|
||||
|
||||
@Component({
|
||||
selector: 'os-particle-display',
|
||||
templateUrl: './particle-display.component.html',
|
||||
styleUrls: ['./particle-display.component.scss'],
|
||||
encapsulation: ViewEncapsulation.None
|
||||
})
|
||||
export class ParticleDisplayComponent extends BaseViewComponentDirective {
|
||||
public resizeSubject = new Subject<ElementSize>();
|
||||
private resizeAuditTime = 200;
|
||||
|
||||
private particleContainer: Container;
|
||||
|
||||
public set particleImage(imageUrl: string) {
|
||||
this.setParticleImage(imageUrl);
|
||||
}
|
||||
|
||||
public set particleLevel(level: number) {
|
||||
this.setParticleLevel(level);
|
||||
}
|
||||
|
||||
private noAutomaticParticles = {
|
||||
value: 0
|
||||
};
|
||||
|
||||
private slowBlinkingOpacity = {
|
||||
value: 0.8,
|
||||
animation: {
|
||||
enable: true,
|
||||
speed: 1,
|
||||
sync: false,
|
||||
minimumValue: 0.3
|
||||
},
|
||||
random: {
|
||||
enable: true,
|
||||
minimumValue: 0.8
|
||||
}
|
||||
};
|
||||
|
||||
private imageOptions: IImageShape = {
|
||||
replace_color: false,
|
||||
replaceColor: false,
|
||||
src: '',
|
||||
width: 24,
|
||||
height: 24
|
||||
};
|
||||
|
||||
private customImageShape = {
|
||||
type: 'image',
|
||||
image: this.imageOptions
|
||||
};
|
||||
|
||||
private charShapeHearth = {
|
||||
type: 'char',
|
||||
options: {
|
||||
char: {
|
||||
fill: true,
|
||||
font: 'Verdana',
|
||||
weight: '200',
|
||||
style: '',
|
||||
value: ['❤']
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private slightlyRandomSize: any = {
|
||||
value: 16,
|
||||
random: {
|
||||
enable: true,
|
||||
minimumValue: 10
|
||||
}
|
||||
};
|
||||
|
||||
private moveUpOptions = {
|
||||
enable: true,
|
||||
direction: 'top',
|
||||
speed: 1.0,
|
||||
angle: {
|
||||
offset: 45,
|
||||
value: 90
|
||||
},
|
||||
gravity: {
|
||||
enable: true,
|
||||
maxSpeed: 1.5,
|
||||
acceleration: -3
|
||||
},
|
||||
outModes: {
|
||||
left: 'bounce',
|
||||
right: 'bounce',
|
||||
top: 'destroy'
|
||||
}
|
||||
};
|
||||
|
||||
private slowRandomRotation = {
|
||||
value: 0,
|
||||
enable: true,
|
||||
direction: 'random',
|
||||
animation: {
|
||||
enable: true,
|
||||
speed: 9
|
||||
},
|
||||
random: {
|
||||
enable: true,
|
||||
minimumValue: 0
|
||||
}
|
||||
};
|
||||
|
||||
private randomColor = {
|
||||
value: 'random'
|
||||
};
|
||||
|
||||
private singleBottomEmitter = [
|
||||
{
|
||||
direction: 'top',
|
||||
rate: {
|
||||
quantity: 0,
|
||||
delay: 0.33
|
||||
},
|
||||
position: {
|
||||
x: 50,
|
||||
y: 100
|
||||
},
|
||||
size: {
|
||||
mode: SizeMode.percent,
|
||||
width: 100
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
public particlesOptions = {
|
||||
fpsLimit: 30,
|
||||
particles: {
|
||||
number: this.noAutomaticParticles,
|
||||
opacity: this.slowBlinkingOpacity,
|
||||
rotate: this.slowRandomRotation,
|
||||
move: this.moveUpOptions,
|
||||
color: this.randomColor,
|
||||
shape: this.charShapeHearth,
|
||||
size: this.slightlyRandomSize
|
||||
},
|
||||
emitters: this.singleBottomEmitter,
|
||||
detectRetina: true
|
||||
};
|
||||
|
||||
public constructor(
|
||||
title: Title,
|
||||
translate: TranslateService,
|
||||
matSnackBar: MatSnackBar,
|
||||
configService: ConfigService,
|
||||
private applauseService: ApplauseService
|
||||
) {
|
||||
super(title, translate, matSnackBar);
|
||||
this.subscriptions.push(
|
||||
this.resizeSubject.pipe(auditTime(this.resizeAuditTime)).subscribe(size => {
|
||||
this.updateParticleContainer(size);
|
||||
}),
|
||||
applauseService.applauseLevelObservable.subscribe(applause => {
|
||||
this.particleLevel = this.calcEmitterLevel(applause || 0);
|
||||
}),
|
||||
configService.get<string>('general_system_applause_particle_image').subscribe(particleImage => {
|
||||
this.particleImage = particleImage || undefined;
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
private setParticleImage(particleImage: string): void {
|
||||
if (particleImage) {
|
||||
this.imageOptions.src = particleImage;
|
||||
(this.particlesOptions.particles.shape as any) = this.customImageShape;
|
||||
} else {
|
||||
(this.particlesOptions.particles.shape as any) = this.charShapeHearth;
|
||||
}
|
||||
if (this.particleContainer) {
|
||||
this.particleContainer.options.particles.load(this.particlesOptions.particles as any);
|
||||
this.refresh();
|
||||
}
|
||||
}
|
||||
|
||||
private calcEmitterLevel(applauseLevel: number): number {
|
||||
if (!applauseLevel) {
|
||||
return 0;
|
||||
}
|
||||
let emitterLevel = this.applauseService.getApplauseQuote(applauseLevel);
|
||||
emitterLevel = Math.ceil(emitterLevel * 10);
|
||||
return emitterLevel;
|
||||
}
|
||||
|
||||
private setParticleLevel(level: number): void {
|
||||
if (this.particleContainer) {
|
||||
const emitters = this.particleContainer.plugins.get('emitters') as Emitters;
|
||||
if (emitters) {
|
||||
emitters.array[0].emitterOptions.rate.quantity = level;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private updateParticleContainer(size: ElementSize): void {
|
||||
if (!size.height || !size.width) {
|
||||
this.stop();
|
||||
} else {
|
||||
this.refresh();
|
||||
}
|
||||
}
|
||||
|
||||
private stop(): void {
|
||||
this.particleContainer?.stop();
|
||||
}
|
||||
|
||||
private refresh(): void {
|
||||
this.particleContainer?.refresh();
|
||||
}
|
||||
|
||||
public particlesLoaded(container: Container): void {
|
||||
this.particleContainer = container;
|
||||
this.refresh();
|
||||
}
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
<div class="progress-wrapper">
|
||||
<mat-icon class="end-icon" *ngIf="endIcon"> {{ endIcon }} </mat-icon>
|
||||
<div class="slot">
|
||||
<ng-content></ng-content>
|
||||
</div>
|
||||
<div class="progress-bar">
|
||||
<div class="buffer bar"></div>
|
||||
<div class="progress bar" [style.height.%]="value"></div>
|
||||
</div>
|
||||
</div>
|
@ -0,0 +1,36 @@
|
||||
$bar-margin: 10px;
|
||||
$bar-border-radius: 15px;
|
||||
|
||||
.progress-wrapper {
|
||||
display: flex;
|
||||
height: 100%;
|
||||
flex-direction: column;
|
||||
|
||||
.end-icon {
|
||||
display: block;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
.progress-bar {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
|
||||
.bar {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
margin-left: $bar-margin;
|
||||
margin-right: $bar-margin;
|
||||
border-radius: $bar-border-radius;
|
||||
}
|
||||
|
||||
.buffer {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.progress {
|
||||
transition: height 0.5s ease;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
@import '~@angular/material/theming';
|
||||
|
||||
@mixin os-progress-theme($theme) {
|
||||
$primary: map-get($theme, primary);
|
||||
$accent: map-get($theme, accent);
|
||||
|
||||
.progress-wrapper {
|
||||
background-color: mat-color($primary);
|
||||
}
|
||||
|
||||
.end-icon {
|
||||
color: mat-color($primary, default-contrast);
|
||||
}
|
||||
|
||||
.slot {
|
||||
color: mat-color($primary, default-contrast);
|
||||
}
|
||||
|
||||
.buffer {
|
||||
background-color: mat-color($primary, darker);
|
||||
}
|
||||
|
||||
.progress {
|
||||
background-color: mat-color($accent);
|
||||
}
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { ProgressComponent } from './progress.component';
|
||||
|
||||
describe('ProgressComponent', () => {
|
||||
let component: ProgressComponent;
|
||||
let fixture: ComponentFixture<ProgressComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ProgressComponent]
|
||||
}).compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(ProgressComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
@ -0,0 +1,14 @@
|
||||
import { Component, Input } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'os-progress',
|
||||
templateUrl: './progress.component.html',
|
||||
styleUrls: ['./progress.component.scss']
|
||||
})
|
||||
export class ProgressComponent {
|
||||
@Input()
|
||||
public value = 0;
|
||||
|
||||
@Input()
|
||||
public endIcon: string;
|
||||
}
|
@ -1,4 +1,5 @@
|
||||
<div class="video-wrapper">
|
||||
<os-particle-display *ngIf="showParticles" class="applause-particles"></os-particle-display>
|
||||
<div class="player-container" [ngClass]="{ hide: !isUrlOnline }">
|
||||
<video #videoPlayer class="video-js" controls preload="none"></video>
|
||||
</div>
|
||||
|
@ -1,7 +1,17 @@
|
||||
.video-wrapper {
|
||||
position: relative;
|
||||
display: flex;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
min-height: 200px;
|
||||
|
||||
.applause-particles {
|
||||
position: absolute;
|
||||
display: block;
|
||||
pointer-events: none !important;
|
||||
width: 100px;
|
||||
height: 100%;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.is-offline-wrapper {
|
||||
width: 100%;
|
||||
|
@ -48,6 +48,9 @@ export class VjsPlayerComponent implements OnInit, OnDestroy {
|
||||
this.checkVideoUrl();
|
||||
}
|
||||
|
||||
@Input()
|
||||
public showParticles: boolean;
|
||||
|
||||
@Output()
|
||||
private started: EventEmitter<void> = new EventEmitter();
|
||||
|
||||
|
@ -1,10 +1,15 @@
|
||||
import { Directive, ElementRef, Input, OnInit } from '@angular/core';
|
||||
|
||||
import { ResizeSensor } from 'css-element-queries';
|
||||
import { Interface } from 'readline';
|
||||
import { Subject } from 'rxjs';
|
||||
|
||||
export interface ElementSize {
|
||||
width: number;
|
||||
height: number;
|
||||
}
|
||||
/**
|
||||
* This directive takes a Subject<void> as input and everytime the surrounding element
|
||||
* This directive takes a Subject<ElementSize> as input and everytime the surrounding element
|
||||
* was resized, the subject is fired.
|
||||
*
|
||||
* Usage:
|
||||
@ -15,7 +20,7 @@ import { Subject } from 'rxjs';
|
||||
})
|
||||
export class ResizedDirective implements OnInit {
|
||||
@Input()
|
||||
public osResized: Subject<void>;
|
||||
public osResized: Subject<ElementSize>;
|
||||
|
||||
/**
|
||||
* Old width, to check, if the width has actually changed.
|
||||
@ -54,7 +59,10 @@ export class ResizedDirective implements OnInit {
|
||||
this.oldHeight = newHeight;
|
||||
|
||||
if (this.osResized) {
|
||||
this.osResized.next();
|
||||
this.osResized.next({
|
||||
width: newWidth,
|
||||
height: newHeight
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -130,6 +130,10 @@ import { JitsiComponent } from './components/jitsi/jitsi.component';
|
||||
import { VjsPlayerComponent } from './components/vjs-player/vjs-player.component';
|
||||
import { LiveStreamComponent } from './components/live-stream/live-stream.component';
|
||||
import { ListOfSpeakersContentComponent } from './components/list-of-speakers-content/list-of-speakers-content.component';
|
||||
import { ApplauseDisplayComponent } from './components/applause-display/applause-display.component';
|
||||
import { ProgressComponent } from './components/progress/progress.component';
|
||||
import { NgParticlesModule } from 'ng-particles';
|
||||
import { ParticleDisplayComponent } from './components/particle-display/particle-display.component';
|
||||
|
||||
/**
|
||||
* Share Module for all "dumb" components and pipes.
|
||||
@ -194,7 +198,8 @@ import { ListOfSpeakersContentComponent } from './components/list-of-speakers-co
|
||||
PblNgridTargetEventsModule,
|
||||
PdfViewerModule,
|
||||
NgxMaterialTimepickerModule,
|
||||
ChartsModule
|
||||
ChartsModule,
|
||||
NgParticlesModule
|
||||
],
|
||||
exports: [
|
||||
FormsModule,
|
||||
@ -364,7 +369,10 @@ import { ListOfSpeakersContentComponent } from './components/list-of-speakers-co
|
||||
JitsiComponent,
|
||||
VjsPlayerComponent,
|
||||
LiveStreamComponent,
|
||||
ListOfSpeakersContentComponent
|
||||
ListOfSpeakersContentComponent,
|
||||
ApplauseDisplayComponent,
|
||||
ProgressComponent,
|
||||
ParticleDisplayComponent
|
||||
],
|
||||
providers: [
|
||||
{
|
||||
|
@ -13,7 +13,6 @@
|
||||
</os-head-bar>
|
||||
|
||||
<mat-card class="spacer-bottom-60" [ngClass]="isEditing ? 'os-form-card' : 'os-card'">
|
||||
<mat-card [ngClass]="isEditing ? 'os-form-card' : 'os-card'">
|
||||
<ng-container *ngIf="!isEditing">
|
||||
<div class="app-content">
|
||||
<h1>{{ startContent.general_event_welcome_title | translate }}</h1>
|
||||
|
@ -9,43 +9,44 @@ $narrow-spacing: (
|
||||
);
|
||||
|
||||
/** Import brand theme */
|
||||
@import './assets/styles/themes/default-dark.scss';
|
||||
@import './assets/styles/themes/default-light.scss';
|
||||
@import './assets/styles/themes/green-dark.scss';
|
||||
@import './assets/styles/themes/green-light.scss';
|
||||
@import './assets/styles/themes/red-dark.scss';
|
||||
@import './assets/styles/themes/red-light.scss';
|
||||
@import './assets/styles/themes/solarized-dark.scss';
|
||||
@import '~assets/styles/themes/default-dark.scss';
|
||||
@import '~assets/styles/themes/default-light.scss';
|
||||
@import '~assets/styles/themes/green-dark.scss';
|
||||
@import '~assets/styles/themes/green-light.scss';
|
||||
@import '~assets/styles/themes/red-dark.scss';
|
||||
@import '~assets/styles/themes/red-light.scss';
|
||||
@import '~assets/styles/themes/solarized-dark.scss';
|
||||
|
||||
/** Global component style definition */
|
||||
@import './assets/styles/global-components-style.scss';
|
||||
@import '~assets/styles/global-components-style.scss';
|
||||
|
||||
/** Import the component-related style sheets here */
|
||||
@import './app/site/site.component.scss-theme.scss';
|
||||
@import './app/shared/components/projector-button/projector-button.component.scss';
|
||||
@import './app/site/agenda/components/list-of-speakers/list-of-speakers.component.scss-theme.scss';
|
||||
@import './app/shared/components/sorting-tree/sorting-tree.component.scss';
|
||||
@import './app/shared/components/global-spinner/global-spinner.component.scss';
|
||||
@import './app/shared/components/tile/tile.component.scss';
|
||||
@import './app/shared/components/block-tile/block-tile.component.scss';
|
||||
@import './app/shared/components/icon-container/icon-container.component.scss';
|
||||
@import './app/site/common/components/start/start.component.scss';
|
||||
@import './app/site/mediafiles/components/mediafile-list/mediafile-list.component.scss-theme.scss';
|
||||
@import './app/site/common/components/super-search/super-search.component.scss';
|
||||
@import './app/shared/components/rounded-input/rounded-input.component.scss';
|
||||
@import './app/shared/components/meta-text-block/meta-text-block.component.scss';
|
||||
@import './app/site/config/components/config-field/config-field.component.scss-theme.scss';
|
||||
@import './app/site/motions/modules/motion-detail/components/amendment-create-wizard/amendment-create-wizard.components.scss-theme.scss';
|
||||
@import './app/site/motions/modules/motion-detail/components/motion-detail-diff/motion-detail-diff.component.scss-theme.scss';
|
||||
@import './app/shared/components/banner/banner.component.scss-theme.scss';
|
||||
@import './app/site/motions/modules/motion-poll/motion-poll/motion-poll.component.scss-theme.scss';
|
||||
@import './app/site/motions/modules/motion-poll/motion-poll-detail/motion-poll-detail.component.scss-theme.scss';
|
||||
@import './app/site/assignments/modules/assignment-poll/components/assignment-poll-detail/assignment-poll-detail-component.scss-theme.scss';
|
||||
@import './app/shared/components/progress-snack-bar/progress-snack-bar.component.scss-theme.scss';
|
||||
@import './app/shared/components/jitsi/jitsi.component.scss-theme.scss';
|
||||
@import './app/shared/components/list-view-table/list-view-table.component.scss-theme.scss';
|
||||
@import './app/site/common/components/user-statistics/user-statistics.component.scss-theme.scss';
|
||||
@import './app/site/login/components/login-wrapper/login-wrapper.component.scss-theme.scss';
|
||||
@import '~app/site/site.component.scss-theme.scss';
|
||||
@import '~app/shared/components/projector-button/projector-button.component.scss';
|
||||
@import '~app/site/agenda/components/list-of-speakers/list-of-speakers.component.scss-theme.scss';
|
||||
@import '~app/shared/components/sorting-tree/sorting-tree.component.scss';
|
||||
@import '~app/shared/components/global-spinner/global-spinner.component.scss';
|
||||
@import '~app/shared/components/tile/tile.component.scss';
|
||||
@import '~app/shared/components/block-tile/block-tile.component.scss';
|
||||
@import '~app/shared/components/icon-container/icon-container.component.scss';
|
||||
@import '~app/site/common/components/start/start.component.scss';
|
||||
@import '~app/site/mediafiles/components/mediafile-list/mediafile-list.component.scss-theme.scss';
|
||||
@import '~app/site/common/components/super-search/super-search.component.scss';
|
||||
@import '~app/shared/components/rounded-input/rounded-input.component.scss';
|
||||
@import '~app/shared/components/meta-text-block/meta-text-block.component.scss';
|
||||
@import '~app/site/config/components/config-field/config-field.component.scss-theme.scss';
|
||||
@import '~app/site/motions/modules/motion-detail/components/amendment-create-wizard/amendment-create-wizard.components.scss-theme.scss';
|
||||
@import '~app/site/motions/modules/motion-detail/components/motion-detail-diff/motion-detail-diff.component.scss-theme.scss';
|
||||
@import '~app/shared/components/banner/banner.component.scss-theme.scss';
|
||||
@import '~app/site/motions/modules/motion-poll/motion-poll/motion-poll.component.scss-theme.scss';
|
||||
@import '~app/site/motions/modules/motion-poll/motion-poll-detail/motion-poll-detail.component.scss-theme.scss';
|
||||
@import '~app/site/assignments/modules/assignment-poll/components/assignment-poll-detail/assignment-poll-detail-component.scss-theme.scss';
|
||||
@import '~app/shared/components/progress-snack-bar/progress-snack-bar.component.scss-theme.scss';
|
||||
@import '~app/shared/components/jitsi/jitsi.component.scss-theme.scss';
|
||||
@import '~app/shared/components/list-view-table/list-view-table.component.scss-theme.scss';
|
||||
@import '~app/site/common/components/user-statistics/user-statistics.component.scss-theme.scss';
|
||||
@import '~app/site/login/components/login-wrapper/login-wrapper.component.scss-theme.scss';
|
||||
@import '~app/shared/components/progress/progress.component.scss-theme.scss';
|
||||
|
||||
/** Mix the component-related style-rules. Every single custom style goes here */
|
||||
@mixin openslides-components-theme($theme) {
|
||||
@ -72,6 +73,7 @@ $narrow-spacing: (
|
||||
@include os-list-view-table-theme($theme);
|
||||
@include os-user-statistics-style($theme);
|
||||
@include os-login-wrapper-theme($theme);
|
||||
@include os-progress-theme($theme);
|
||||
}
|
||||
|
||||
.openslides-default-light-theme {
|
||||
@ -126,7 +128,7 @@ $narrow-spacing: (
|
||||
/**
|
||||
* Custom configuration for light themes
|
||||
*/
|
||||
[class^="openslides-"][class*="-light-theme"] {
|
||||
[class^='openslides-'][class*='-light-theme'] {
|
||||
.logo-container {
|
||||
img.dark {
|
||||
display: none;
|
||||
@ -140,7 +142,7 @@ $narrow-spacing: (
|
||||
/**
|
||||
* Custom configuration for dark themes
|
||||
*/
|
||||
[class^="openslides-"][class*="-dark-theme"] {
|
||||
[class^='openslides-'][class*='-dark-theme'] {
|
||||
color: white;
|
||||
.logo-container {
|
||||
img.dark {
|
||||
|
@ -1,4 +1,4 @@
|
||||
@import './assets/styles/color-palettes/os-blue';
|
||||
@import '.~assets/styles/color-palettes/os-blue';
|
||||
|
||||
$openslides-primary: mat-palette($openslides-blue);
|
||||
$openslides-accent: mat-palette($mat-light-blue);
|
||||
|
@ -1,4 +1,4 @@
|
||||
@import './assets/styles/color-palettes/os-blue';
|
||||
@import '~assets/styles/color-palettes/os-blue';
|
||||
|
||||
// Generate paletes using: https://material.io/design/color/
|
||||
// default values for mat-palette: $default: 500, $lighter: 100, $darker: 700.
|
||||
|
@ -1,4 +1,4 @@
|
||||
@import './assets/styles/color-palettes/os-green.scss';
|
||||
@import '~assets/styles/color-palettes/os-green.scss';
|
||||
|
||||
$openslides-primary: mat-palette($openslides-green);
|
||||
$openslides-accent: mat-palette($mat-amber);
|
||||
|
@ -1,4 +1,4 @@
|
||||
@import './assets/styles/color-palettes/os-green.scss';
|
||||
@import '~assets/styles/color-palettes/os-green.scss';
|
||||
|
||||
$openslides-primary: mat-palette($openslides-green);
|
||||
$openslides-accent: mat-palette($mat-amber);
|
||||
|
@ -1,4 +1,4 @@
|
||||
@import './assets/styles/color-palettes/os-red.scss';
|
||||
@import '~assets/styles/color-palettes/os-red.scss';
|
||||
|
||||
$openslides-primary: mat-palette($openslides-primary-red, 500, 300, 900);
|
||||
$openslides-accent: mat-palette($mat-amber);
|
||||
|
@ -1,4 +1,4 @@
|
||||
@import './assets/styles/color-palettes/os-red.scss';
|
||||
@import '~assets/styles/color-palettes/os-red.scss';
|
||||
|
||||
$openslides-primary: mat-palette($openslides-primary-red, 500, 300, 900);
|
||||
$openslides-accent: mat-palette($mat-amber);
|
||||
|
@ -1,5 +1,5 @@
|
||||
@import './assets/styles/color-palettes/os-cyan.scss';
|
||||
@import './assets/styles/color-palettes/os-gray.scss';
|
||||
@import '~assets/styles/color-palettes/os-cyan.scss';
|
||||
@import '~assets/styles/color-palettes/os-gray.scss';
|
||||
|
||||
$openslides-primary: mat-palette($openslides-grey);
|
||||
$openslides-accent: mat-palette($openslides-cyan);
|
||||
|
226
client/src/assets/svg/clapping_hands.svg
Normal file
226
client/src/assets/svg/clapping_hands.svg
Normal file
@ -0,0 +1,226 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
version="1.1"
|
||||
id="svg2"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
sodipodi:docname="clapping_hands.svg"
|
||||
inkscape:version="1.0.1 (3bc2e813f5, 2020-09-07)">
|
||||
<metadata
|
||||
id="metadata8">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<defs
|
||||
id="defs6">
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
id="linearGradient1782">
|
||||
<stop
|
||||
style="stop-color:#0c2c40;stop-opacity:1"
|
||||
offset="0"
|
||||
id="stop1778" />
|
||||
<stop
|
||||
style="stop-color:#2ca2ec;stop-opacity:1"
|
||||
offset="1"
|
||||
id="stop1780" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
id="linearGradient1722">
|
||||
<stop
|
||||
style="stop-color:#f56055;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop1718" />
|
||||
<stop
|
||||
style="stop-color:#f56055;stop-opacity:0;"
|
||||
offset="1"
|
||||
id="stop1720" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
id="linearGradient1505">
|
||||
<stop
|
||||
style="stop-color:#ffffff;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop1501" />
|
||||
<stop
|
||||
style="stop-color:#ffffff;stop-opacity:0;"
|
||||
offset="1"
|
||||
id="stop1503" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient1505"
|
||||
id="linearGradient1507"
|
||||
x1="-72.521545"
|
||||
y1="-354.46579"
|
||||
x2="-72.521545"
|
||||
y2="-220.85248"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="translate(4.5351563e-5,0.25821471)" />
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient1722"
|
||||
id="linearGradient1724"
|
||||
x1="1637.6523"
|
||||
y1="687.29828"
|
||||
x2="1637.6523"
|
||||
y2="726.80103"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="matrix(1,0,0,1.708266,4.6566406e-4,-480.80936)" />
|
||||
<radialGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient1782"
|
||||
id="radialGradient1784"
|
||||
cx="773.6098"
|
||||
cy="-57.234745"
|
||||
fx="773.6098"
|
||||
fy="-57.234745"
|
||||
r="507.56699"
|
||||
gradientTransform="matrix(0.78017444,0,0,0.14366746,326.05843,630.13805)"
|
||||
gradientUnits="userSpaceOnUse" />
|
||||
</defs>
|
||||
<sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1080"
|
||||
id="namedview4"
|
||||
showgrid="false"
|
||||
showguides="true"
|
||||
inkscape:guide-bbox="true"
|
||||
inkscape:snap-bbox="true"
|
||||
inkscape:bbox-paths="true"
|
||||
inkscape:bbox-nodes="true"
|
||||
inkscape:snap-bbox-edge-midpoints="true"
|
||||
inkscape:snap-bbox-midpoints="true"
|
||||
inkscape:object-paths="true"
|
||||
inkscape:snap-intersection-paths="true"
|
||||
inkscape:snap-smooth-nodes="true"
|
||||
inkscape:snap-midpoints="true"
|
||||
inkscape:snap-object-midpoints="true"
|
||||
inkscape:zoom="22.627417"
|
||||
inkscape:cx="19.730628"
|
||||
inkscape:cy="14.778162"
|
||||
inkscape:window-x="1920"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="g2182"
|
||||
inkscape:snap-global="true"
|
||||
inkscape:document-rotation="0">
|
||||
<sodipodi:guide
|
||||
position="-8.5967379,25.493463"
|
||||
orientation="1,0"
|
||||
id="guide1229" />
|
||||
<sodipodi:guide
|
||||
position="-5.4316862,28.164944"
|
||||
orientation="0,-1"
|
||||
id="guide1231" />
|
||||
</sodipodi:namedview>
|
||||
<g
|
||||
id="g2441"
|
||||
transform="matrix(0.93524686,0,0,0.93524686,-1220.0337,-836.48596)"
|
||||
style="fill:#000000;fill-opacity:1">
|
||||
<g
|
||||
transform="matrix(0.76088502,0,0,0.76030764,311.54674,218.13833)"
|
||||
id="g2397"
|
||||
style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.13311">
|
||||
<g
|
||||
id="g1964-4"
|
||||
transform="rotate(90,218.24928,1114.2299)"
|
||||
style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.13311">
|
||||
<g
|
||||
id="g1948-2"
|
||||
style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.13311">
|
||||
<g
|
||||
id="g1946-2"
|
||||
style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.13311">
|
||||
<g
|
||||
id="g1944-4"
|
||||
style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.13311">
|
||||
<path
|
||||
id="path1942-5"
|
||||
d="M 23,5.5 V 20 c 0,2.2 -1.8,4 -4,4 H 11.7 C 10.62,24 9.6,23.57 8.85,22.81 L 1,14.83 c 0,0 1.26,-1.23 1.3,-1.25 0.22,-0.19 0.49,-0.29 0.79,-0.29 0.22,0 0.42,0.06 0.6,0.16 C 3.73,13.46 8,15.91 8,15.91 V 4 C 8,3.17 8.67,2.5 9.5,2.5 10.33,2.5 11,3.17 11,4 v 7 h 1 V 1.5 C 12,0.67 12.67,0 13.5,0 14.33,0 15,0.67 15,1.5 V 11 h 1 V 2.5 C 16,1.67 16.67,1 17.5,1 18.33,1 19,1.67 19,2.5 V 11 h 1 V 5.5 C 20,4.67 20.67,4 21.5,4 22.33,4 23,4.67 23,5.5 Z"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.13311" />
|
||||
</g>
|
||||
<g
|
||||
transform="translate(5.4402324e-7,4.0928558e-5)"
|
||||
id="g1944-4-0"
|
||||
style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.13311" />
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g
|
||||
style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.894784;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
transform="matrix(1.1175883,0,0,1.1175883,-155.61227,-104.47506)"
|
||||
id="g2182">
|
||||
<g
|
||||
id="g860"
|
||||
style="opacity:1;stroke-width:0.855545"
|
||||
transform="matrix(1.0458649,0,0,1.0458649,-60.689016,-41.511804)">
|
||||
<path
|
||||
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-variant-east-asian:normal;font-feature-settings:normal;font-variation-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;shape-margin:0;inline-size:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.855541;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate;stop-color:#000000;stop-opacity:1"
|
||||
d="m 1326.2009,898.5852 -2.6143,2.61248 c 0.054,0.0477 0.1074,0.097 0.1593,0.14738 0.05,0.0525 0.099,0.10627 0.1459,0.16112 l 2.6168,-2.61488 z m -4.6234,-2.21282 -0.434,-3e-5 v 3.69727 c 0.1446,-0.006 0.2894,-0.006 0.434,4e-5 z m 7.1449,6.80056 -3.7002,3e-5 c 0.013,0.14447 0.013,0.28922 2e-4,0.43368 l 3.7,-2e-5 z"
|
||||
id="path862" />
|
||||
<path
|
||||
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-variant-east-asian:normal;font-feature-settings:normal;font-variation-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;shape-margin:0;inline-size:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.855541;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate;stop-color:#000000;stop-opacity:1"
|
||||
d="m 1321.1426,895.92578 a 0.44743473,0.44743473 0 0 0 -0.4473,0.44727 v 3.69726 a 0.44743473,0.44743473 0 0 0 0.4668,0.44727 c 0.1322,-0.005 0.2643,-0.006 0.3965,0 a 0.44743473,0.44743473 0 0 0 0.4668,-0.44727 v -3.69726 a 0.44743473,0.44743473 0 0 0 -0.4473,-0.44727 z m 5.0703,2.21289 a 0.44743473,0.44743473 0 0 0 -0.3281,0.13086 l -2.6153,2.61133 a 0.44743473,0.44743473 0 0 0 0.021,0.65234 c 0.049,0.0433 0.096,0.0877 0.1426,0.13282 0.041,0.0437 0.082,0.0873 0.1191,0.13086 a 0.44743473,0.44743473 0 0 0 0.6563,0.0254 l 2.6152,-2.61524 a 0.44743473,0.44743473 0 0 0 0,-0.63281 l -0.3086,-0.30664 a 0.44743473,0.44743473 0 0 0 -0.3027,-0.12891 z m -1.1914,4.58594 a 0.44743473,0.44743473 0 0 0 -0.4453,0.48828 c 0.011,0.1179 0.011,0.23523 0,0.35352 a 0.44743473,0.44743473 0 0 0 0.4453,0.48828 h 3.7012 a 0.44743473,0.44743473 0 0 0 0.4472,-0.44727 v -0.43359 a 0.44743473,0.44743473 0 0 0 -0.4472,-0.44922 z"
|
||||
id="path864" />
|
||||
</g>
|
||||
</g>
|
||||
<g
|
||||
transform="matrix(0.86183474,0,0,0.86183474,180.09301,126.9712)"
|
||||
id="g2397-4"
|
||||
style="fill:#000000;fill-opacity:1">
|
||||
<g
|
||||
id="g1964-4-5"
|
||||
transform="rotate(90,218.24928,1114.2299)"
|
||||
style="fill:#000000;fill-opacity:1">
|
||||
<g
|
||||
id="g1948-2-0"
|
||||
style="fill:#000000;fill-opacity:1">
|
||||
<g
|
||||
id="g1946-2-57"
|
||||
style="fill:#000000;fill-opacity:1">
|
||||
<g
|
||||
id="g1944-4-32"
|
||||
style="fill:#000000;fill-opacity:1" />
|
||||
<g
|
||||
transform="translate(5.4402324e-7,4.0928558e-5)"
|
||||
id="g1944-4-0-2"
|
||||
style="fill:#000000;fill-opacity:1" />
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<path
|
||||
style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.806028"
|
||||
d="M 6.5569914,2.9864148 0.87698077,8.56734 C 0.33613684,9.1006455 0.03031238,9.8273997 0.03031238,10.595361 v 5.189794 c 0,1.181491 0.73189623,2.199492 1.76396712,2.62764 -0.00888,-0.08924 -0.02737,-0.174463 -0.02737,-0.265356 v -5.191514 c 0,-0.955146 0.3829864,-1.864797 1.0569667,-2.529425 L 6.3743238,6.9356205 C 6.8910656,6.0338659 7.5323796,4.9178609 7.538295,4.8989906 7.6098007,4.7710011 7.6521007,4.6281034 7.6521007,4.4716724 7.6521007,4.2583484 7.5805947,4.066391 7.4451576,3.90996 7.4310282,3.8814651 6.5571713,2.9864148 6.5571713,2.9864148 Z m 3.8401146,4.9778558 c -0.02737,0.048433 -0.0459,0.080279 -0.07769,0.1361228 C 10.154314,8.3899921 9.9344832,8.7732978 9.7158851,9.1548918 9.5822184,9.3881797 9.5695934,9.4091143 9.4485527,9.6201187 h 5.6800103 c 0.116884,-0.1710937 0.201821,-0.3659799 0.201821,-0.5892814 0,-0.5901895 -0.476748,-1.0665667 -1.067387,-1.0665667 z"
|
||||
id="path1942-5-3" />
|
||||
</svg>
|
After Width: | Height: | Size: 12 KiB |
@ -1,18 +1,18 @@
|
||||
/** theming */
|
||||
@import './assets/styles/component-themes.scss';
|
||||
@import '~assets/styles/component-themes.scss';
|
||||
|
||||
/** fonts */
|
||||
@import './assets/styles/fonts.scss';
|
||||
@import '~assets/styles/fonts.scss';
|
||||
@import '~material-icon-font/dist/Material-Icons.css';
|
||||
|
||||
/** Videojs */
|
||||
@import '~video.js/dist/video-js.css';
|
||||
|
||||
/** Load projector specific SCSS values */
|
||||
@import './assets/styles/projector.scss';
|
||||
@import '~assets/styles/projector.scss';
|
||||
|
||||
/** Load global scss variables and device mixing */
|
||||
@import './assets/styles/variables.scss';
|
||||
@import '~assets/styles/variables.scss';
|
||||
|
||||
.pbl-ngrid-cell {
|
||||
.fill {
|
||||
@ -788,3 +788,7 @@ button.mat-menu-item.selected {
|
||||
.hide {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.hide-height {
|
||||
height: 0 !important;
|
||||
}
|
||||
|
@ -15,7 +15,6 @@ RUN apt-get -y update && apt-get install --no-install-recommends -y \
|
||||
libxmlsec1-dev \
|
||||
libxmlsec1-openssl \
|
||||
pkg-config
|
||||
|
||||
RUN rm -rf /var/lib/apt/lists/*
|
||||
|
||||
COPY requirements /app/requirements
|
||||
|
@ -169,6 +169,85 @@ def get_config_variables():
|
||||
subgroup="Live conference",
|
||||
)
|
||||
|
||||
# Applause
|
||||
|
||||
yield ConfigVariable(
|
||||
name="general_system_applause_enable",
|
||||
default_value=False,
|
||||
input_type="boolean",
|
||||
label="Enable virtual applause",
|
||||
help_text="Shows a 'Send applause' icon in the live stream bar",
|
||||
weight=170,
|
||||
subgroup="Virtual applause",
|
||||
)
|
||||
|
||||
yield ConfigVariable(
|
||||
name="general_system_applause_type",
|
||||
default_value="applause-type-bar",
|
||||
input_type="choice",
|
||||
choices=(
|
||||
{
|
||||
"value": "applause-type-bar",
|
||||
"display_name": "Bar",
|
||||
},
|
||||
{
|
||||
"value": "applause-type-particles",
|
||||
"display_name": "Particles",
|
||||
},
|
||||
),
|
||||
label="Applause Type",
|
||||
weight=171,
|
||||
subgroup="Virtual applause",
|
||||
)
|
||||
|
||||
yield ConfigVariable(
|
||||
name="general_system_applause_show_level",
|
||||
default_value=False,
|
||||
input_type="boolean",
|
||||
label="Show applause amount",
|
||||
weight=172,
|
||||
subgroup="Virtual applause",
|
||||
)
|
||||
|
||||
yield ConfigVariable(
|
||||
name="general_system_applause_min_amount",
|
||||
default_value=1,
|
||||
input_type="integer",
|
||||
label="Lowest applause amount",
|
||||
help_text="Lowest amount required for OpenSlides to recognize applause",
|
||||
weight=173,
|
||||
subgroup="Virtual applause",
|
||||
)
|
||||
|
||||
yield ConfigVariable(
|
||||
name="general_system_applause_max_amount",
|
||||
default_value=0,
|
||||
input_type="integer",
|
||||
label="Highest applause amount",
|
||||
help_text="Defines the maximum deflection of the amount. Entering zero will use the amount of present users instead.",
|
||||
weight=174,
|
||||
subgroup="Virtual applause",
|
||||
)
|
||||
|
||||
yield ConfigVariable(
|
||||
name="general_system_stream_applause_timeout",
|
||||
default_value=5,
|
||||
input_type="integer",
|
||||
label="Applause timeout in seconds",
|
||||
help_text="Determines how long a user has to wait to applaud again. Also determines the time in which applause is collected",
|
||||
weight=175,
|
||||
subgroup="Virtual applause",
|
||||
)
|
||||
|
||||
yield ConfigVariable(
|
||||
name="general_system_applause_particle_image",
|
||||
default_value="",
|
||||
label="Applause particle image url",
|
||||
help_text="Shows the given image as applause particle. Recommended image format: 24x24px, PNG, JPG or SVG",
|
||||
weight=176,
|
||||
subgroup="Virtual applause",
|
||||
)
|
||||
|
||||
# General System
|
||||
|
||||
yield ConfigVariable(
|
||||
|
Loading…
Reference in New Issue
Block a user