added jitsi api + improvements
This commit is contained in:
parent
56362da724
commit
ffe9e6f5ab
@ -43,7 +43,6 @@
|
|||||||
"optimization": true,
|
"optimization": true,
|
||||||
"outputHashing": "all",
|
"outputHashing": "all",
|
||||||
"sourceMap": false,
|
"sourceMap": false,
|
||||||
"extractCss": true,
|
|
||||||
"namedChunks": false,
|
"namedChunks": false,
|
||||||
"extractLicenses": true,
|
"extractLicenses": true,
|
||||||
"vendorChunk": false,
|
"vendorChunk": false,
|
||||||
|
7051
package-lock.json
generated
7051
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
37
package.json
37
package.json
@ -11,39 +11,40 @@
|
|||||||
},
|
},
|
||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@angular/animations": "~10.1.5",
|
"@angular-material-components/datetime-picker": "^5.1.0",
|
||||||
"@angular/cdk": "^10.2.4",
|
"@angular/animations": "~11.2.10",
|
||||||
"@angular/common": "~10.1.5",
|
"@angular/cdk": "^11.2.9",
|
||||||
"@angular/compiler": "~10.1.5",
|
"@angular/common": "~11.2.10",
|
||||||
"@angular/core": "~10.1.5",
|
"@angular/compiler": "~11.2.10",
|
||||||
|
"@angular/core": "~11.2.10",
|
||||||
"@angular/flex-layout": "^11.0.0-beta.33",
|
"@angular/flex-layout": "^11.0.0-beta.33",
|
||||||
"@angular/forms": "~10.1.5",
|
"@angular/forms": "~11.2.10",
|
||||||
"@angular/material": "^10.2.4",
|
"@angular/material": "^11.2.9",
|
||||||
"@angular/material-moment-adapter": "^10.2.7",
|
"@angular/material-moment-adapter": "^11.2.9",
|
||||||
"@angular/platform-browser": "~10.1.5",
|
"@angular/platform-browser": "~11.2.10",
|
||||||
"@angular/platform-browser-dynamic": "~10.1.5",
|
"@angular/platform-browser-dynamic": "~11.2.10",
|
||||||
"@angular/router": "~10.1.5",
|
"@angular/router": "~11.2.10",
|
||||||
"angularx-qrcode": "^10.0.11",
|
"angularx-qrcode": "^10.0.11",
|
||||||
"moment": "^2.29.1",
|
"moment": "^2.29.1",
|
||||||
"openpgp": "^4.10.8",
|
"openpgp": "^4.10.8",
|
||||||
"rxjs": "~6.6.0",
|
"rxjs": "~6.6.7",
|
||||||
"tslib": "^2.0.0",
|
"tslib": "^2.0.0",
|
||||||
"unique-names-generator": "^4.3.1",
|
"unique-names-generator": "^4.3.1",
|
||||||
"zone.js": "~0.10.2"
|
"zone.js": "~0.10.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@angular-devkit/build-angular": "^0.1002.0",
|
"@angular-devkit/build-angular": "^0.1102.9",
|
||||||
"@angular/cli": "~10.1.6",
|
"@angular/cli": "~11.2.9",
|
||||||
"@angular/compiler-cli": "~10.1.5",
|
"@angular/compiler-cli": "~11.2.10",
|
||||||
"@angular/localize": "^10.1.5",
|
"@angular/localize": "^11.2.10",
|
||||||
"@types/jasmine": "~3.5.0",
|
"@types/jasmine": "~3.6.0",
|
||||||
"@types/jasminewd2": "~2.0.3",
|
"@types/jasminewd2": "~2.0.3",
|
||||||
"@types/node": "^12.12.67",
|
"@types/node": "^12.12.67",
|
||||||
"@types/openpgp": "^4.4.14",
|
"@types/openpgp": "^4.4.14",
|
||||||
"codelyzer": "^6.0.0",
|
"codelyzer": "^6.0.0",
|
||||||
"jasmine-core": "~3.6.0",
|
"jasmine-core": "~3.6.0",
|
||||||
"jasmine-spec-reporter": "~5.0.0",
|
"jasmine-spec-reporter": "~5.0.0",
|
||||||
"karma": "~5.0.0",
|
"karma": "~6.3.2",
|
||||||
"karma-chrome-launcher": "~3.1.0",
|
"karma-chrome-launcher": "~3.1.0",
|
||||||
"karma-coverage-istanbul-reporter": "~3.0.2",
|
"karma-coverage-istanbul-reporter": "~3.0.2",
|
||||||
"karma-jasmine": "~4.0.0",
|
"karma-jasmine": "~4.0.0",
|
||||||
|
@ -20,6 +20,7 @@ import {SecurityComponent} from './pages/account/security/security.component';
|
|||||||
import {UnavailableComponent} from './pages/unavailable/unavailable.component';
|
import {UnavailableComponent} from './pages/unavailable/unavailable.component';
|
||||||
import {NotfoundComponent} from './pages/notfound/notfound.component';
|
import {NotfoundComponent} from './pages/notfound/notfound.component';
|
||||||
import {UserComponent} from './pages/user/user.component'
|
import {UserComponent} from './pages/user/user.component'
|
||||||
|
import {JitsiComponent} from './pages/jitsi/jitsi.component'
|
||||||
import {AliasesComponent} from './pages/account/aliases/aliases.component';
|
import {AliasesComponent} from './pages/account/aliases/aliases.component';
|
||||||
import {DomainsComponent} from './pages/account/domains/domains.component';
|
import {DomainsComponent} from './pages/account/domains/domains.component';
|
||||||
|
|
||||||
@ -48,13 +49,14 @@ const routes: Routes = [
|
|||||||
},
|
},
|
||||||
{path: 'register', component: RegisterComponent, canActivate: [AnonymousGuard]},
|
{path: 'register', component: RegisterComponent, canActivate: [AnonymousGuard]},
|
||||||
{path: 'tokens', component: TokensComponent, canActivate: [AuthGuard]},
|
{path: 'tokens', component: TokensComponent, canActivate: [AuthGuard]},
|
||||||
|
{path: 'jitsi', component: JitsiComponent, canActivate: [AuthenticatedGuard]},
|
||||||
{path: 'unavailable', component: UnavailableComponent},
|
{path: 'unavailable', component: UnavailableComponent},
|
||||||
{path: 'p/:username', component: UserComponent, canActivate: [AuthUpdateGuard]},
|
{path: 'p/:username', component: UserComponent, canActivate: [AuthUpdateGuard]},
|
||||||
{path: '**', component: NotfoundComponent, pathMatch: 'full', canActivate: [AuthUpdateGuard]},
|
{path: '**', component: NotfoundComponent, pathMatch: 'full', canActivate: [AuthUpdateGuard]},
|
||||||
];
|
];
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [RouterModule.forRoot(routes, {onSameUrlNavigation: 'reload'})],
|
imports: [RouterModule.forRoot(routes, { onSameUrlNavigation: 'reload', relativeLinkResolution: 'legacy' })],
|
||||||
exports: [RouterModule]
|
exports: [RouterModule]
|
||||||
})
|
})
|
||||||
export class AppRoutingModule {}
|
export class AppRoutingModule {}
|
||||||
|
@ -19,6 +19,7 @@ export class AppComponent {
|
|||||||
darkTheme = "false";
|
darkTheme = "false";
|
||||||
title = 'we.bstly';
|
title = 'we.bstly';
|
||||||
currentLocale: String;
|
currentLocale: String;
|
||||||
|
datetimeformat: String;
|
||||||
locales;
|
locales;
|
||||||
auth;
|
auth;
|
||||||
|
|
||||||
@ -34,7 +35,7 @@ export class AppComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
|
this.datetimeformat = this.i18n.get('format.datetime', []);
|
||||||
this.currentLocale = this.i18n.getLocale();
|
this.currentLocale = this.i18n.getLocale();
|
||||||
this.locales = this.i18n.getLocales();
|
this.locales = this.i18n.getLocales();
|
||||||
this.authService.auth.subscribe(data => {
|
this.authService.auth.subscribe(data => {
|
||||||
|
@ -37,6 +37,7 @@ import {NotfoundComponent} from './pages/notfound/notfound.component';
|
|||||||
import {HtmlComponent} from './utils/html/html.component';
|
import {HtmlComponent} from './utils/html/html.component';
|
||||||
import {ConfirmDialog} from './ui/confirm/confirm.component'
|
import {ConfirmDialog} from './ui/confirm/confirm.component'
|
||||||
import {UserComponent} from './pages/user/user.component'
|
import {UserComponent} from './pages/user/user.component'
|
||||||
|
import {JitsiComponent, JitsiShareDialog} from './pages/jitsi/jitsi.component'
|
||||||
|
|
||||||
|
|
||||||
import {I18nService} from './services/i18n.service';
|
import {I18nService} from './services/i18n.service';
|
||||||
@ -89,7 +90,8 @@ export class XhrInterceptor implements HttpInterceptor {
|
|||||||
NotfoundComponent,
|
NotfoundComponent,
|
||||||
HtmlComponent,
|
HtmlComponent,
|
||||||
ConfirmDialog,
|
ConfirmDialog,
|
||||||
UserComponent
|
UserComponent,
|
||||||
|
JitsiComponent, JitsiShareDialog
|
||||||
],
|
],
|
||||||
imports: [
|
imports: [
|
||||||
BrowserModule,
|
BrowserModule,
|
||||||
|
@ -29,7 +29,7 @@ export class AuthGuard implements CanActivate {
|
|||||||
return this.authService.getAuth().then(response => {
|
return this.authService.getAuth().then(response => {
|
||||||
return true;
|
return true;
|
||||||
}).catch(function(error) {
|
}).catch(function(error) {
|
||||||
return that.router.parseUrl('/unavailable');
|
return that.router.parseUrl('/unavailable?target=' + state.url);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -44,11 +44,9 @@ export class AuthenticatedGuard implements CanActivate {
|
|||||||
const that = this;
|
const that = this;
|
||||||
return this.authService.getAuth().then((data: any) => {
|
return this.authService.getAuth().then((data: any) => {
|
||||||
if(!data.authenticated) {
|
if(!data.authenticated) {
|
||||||
this.router.navigateByUrl('/login');
|
return that.router.parseUrl('/login?target=' + state.url);
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
this.profileService.get(["locale", "darkTheme"]).subscribe((profileFields: any) => {
|
this.profileService.get(["locale", "darkTheme"]).subscribe((profileFields: any) => {
|
||||||
let updateLocale = false;
|
let updateLocale = false;
|
||||||
let darktheme = 'false';
|
let darktheme = 'false';
|
||||||
@ -76,7 +74,7 @@ export class AuthenticatedGuard implements CanActivate {
|
|||||||
|
|
||||||
return true;
|
return true;
|
||||||
}).catch(function(error) {
|
}).catch(function(error) {
|
||||||
return that.router.parseUrl('/unavailable');
|
return that.router.parseUrl('/unavailable?target=' + state.url);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -96,7 +94,7 @@ export class AnonymousGuard implements CanActivate {
|
|||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}).catch(function(error) {
|
}).catch(function(error) {
|
||||||
return that.router.parseUrl('/unavailable');
|
return that.router.parseUrl('/unavailable?target=' + state.url);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -45,6 +45,12 @@ import { MatTableModule } from '@angular/material/table';
|
|||||||
import {MatMomentDateModule} from '@angular/material-moment-adapter';
|
import {MatMomentDateModule} from '@angular/material-moment-adapter';
|
||||||
import {FlexLayoutModule} from '@angular/flex-layout';
|
import {FlexLayoutModule} from '@angular/flex-layout';
|
||||||
|
|
||||||
|
import {
|
||||||
|
NgxMatDatetimePickerModule,
|
||||||
|
NgxMatNativeDateModule,
|
||||||
|
NgxMatTimepickerModule
|
||||||
|
} from '@angular-material-components/datetime-picker';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
declarations: [],
|
declarations: [],
|
||||||
imports: [
|
imports: [
|
||||||
@ -85,7 +91,10 @@ import { FlexLayoutModule } from '@angular/flex-layout';
|
|||||||
MatSortModule,
|
MatSortModule,
|
||||||
MatTableModule,
|
MatTableModule,
|
||||||
MatMomentDateModule,
|
MatMomentDateModule,
|
||||||
FlexLayoutModule
|
FlexLayoutModule,
|
||||||
|
NgxMatDatetimePickerModule,
|
||||||
|
NgxMatNativeDateModule,
|
||||||
|
NgxMatTimepickerModule
|
||||||
],
|
],
|
||||||
exports: [
|
exports: [
|
||||||
MatAutocompleteModule,
|
MatAutocompleteModule,
|
||||||
@ -123,7 +132,10 @@ import { FlexLayoutModule } from '@angular/flex-layout';
|
|||||||
MatPaginatorModule,
|
MatPaginatorModule,
|
||||||
MatSortModule,
|
MatSortModule,
|
||||||
MatTableModule,
|
MatTableModule,
|
||||||
FlexLayoutModule
|
FlexLayoutModule,
|
||||||
|
NgxMatDatetimePickerModule,
|
||||||
|
NgxMatNativeDateModule,
|
||||||
|
NgxMatTimepickerModule
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
export class MaterialModule {}
|
export class MaterialModule {}
|
||||||
|
94
src/app/pages/jitsi/jitsi.component.html
Normal file
94
src/app/pages/jitsi/jitsi.component.html
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
<h3>{{'jitsi.rooms' | i18n}}</h3>
|
||||||
|
|
||||||
|
<table mat-table matSort [dataSource]="jitsiRooms" (matSortChange)="sortData($event)">
|
||||||
|
|
||||||
|
<ng-container matColumnDef="room">
|
||||||
|
<th mat-header-cell *matHeaderCellDef mat-sort-header="room"> {{'jitsi.rooms.room' | i18n}} </th>
|
||||||
|
<td mat-cell *matCellDef="let jitsiRoom">
|
||||||
|
<a mat-button color="accent" href="{{ jitsiRoom.url }}" target="_blank">{{ jitsiRoom.room }}<mat-icon
|
||||||
|
style="font-size: 1em;">open_in_new
|
||||||
|
</mat-icon></a> <button mat-icon-button (click)="share(jitsiRoom)">
|
||||||
|
<mat-icon>share</mat-icon>
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<ng-container matColumnDef="starts">
|
||||||
|
<th mat-header-cell *matHeaderCellDef mat-sort-header="starts"> {{'jitsi.rooms.starts' | i18n}} </th>
|
||||||
|
<td mat-cell *matCellDef="let jitsiRoom">{{ jitsiRoom.starts | date:datetimeformat}} </td>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<ng-container matColumnDef="expires">
|
||||||
|
<th mat-header-cell *matHeaderCellDef mat-sort-header="expires"> {{'jitsi.rooms.expires' | i18n}} </th>
|
||||||
|
<td mat-cell *matCellDef="let jitsiRoom"> {{ jitsiRoom.expires | date:datetimeformat}} </td>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<ng-container matColumnDef="moderationUrl">
|
||||||
|
<th mat-header-cell *matHeaderCellDef mat-sort-header="moderationUrl"> {{'jitsi.rooms.moderationUrl' | i18n}} </th>
|
||||||
|
<td mat-cell *matCellDef="let jitsiRoom">
|
||||||
|
<a mat-button color="primary" class="url" href="{{ jitsiRoom.moderationUrl}}" target="_blank">{{
|
||||||
|
jitsiRoom.moderationUrl }}
|
||||||
|
<mat-icon style="font-size: 1em;">open_in_new
|
||||||
|
</mat-icon>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<ng-container matColumnDef="delete">
|
||||||
|
<th mat-header-cell *matHeaderCellDef class="align-right"> {{'jitsi.rooms.delete' | i18n}} </th>
|
||||||
|
<td mat-cell *matCellDef="let jitsiRoom" class="text-right">
|
||||||
|
<a mat-icon-button>
|
||||||
|
<mat-icon (click)="confirmDelete(jitsiRoom)">delete</mat-icon>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<tr mat-header-row *matHeaderRowDef="jitsiRoomsColumns"></tr>
|
||||||
|
<tr mat-row *matRowDef="let myRowData; columns: jitsiRoomsColumns"></tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<form [formGroup]="form" (ngSubmit)="create()" #formDirective="ngForm">
|
||||||
|
<mat-card>
|
||||||
|
<mat-card-content>
|
||||||
|
<p>{{'jitsi.rooms.info' | i18n}}</p>
|
||||||
|
<p *ngIf="!jitsiRoomsQuota">{{'jitsi.rooms.noQuota' | i18n}}</p>
|
||||||
|
<div *ngIf="jitsiRoomsQuota">
|
||||||
|
<p>{{'jitsi.rooms.left' | i18n:jitsiRoomsQuota}}</p>
|
||||||
|
<mat-form-field>
|
||||||
|
<input matInput placeholder="{{'jitsi.rooms.room' | i18n}}" formControlName="room"
|
||||||
|
[(ngModel)]="jitsiRoom.room" required pattern="[a-zA-Z0-9]+">
|
||||||
|
<mat-error>
|
||||||
|
{{'jitsi.rooms.error.room' | i18n}}
|
||||||
|
</mat-error>
|
||||||
|
</mat-form-field>
|
||||||
|
|
||||||
|
<mat-form-field>
|
||||||
|
<input matInput [ngxMatDatetimePicker]="expiresPicker" [(ngModel)]="jitsiRoom.expires"
|
||||||
|
formControlName="expires" placeholder="{{'jitsi.rooms.expires' | i18n}}" required>
|
||||||
|
<mat-datepicker-toggle matSuffix [for]="expiresPicker"></mat-datepicker-toggle>
|
||||||
|
<ngx-mat-datetime-picker #expiresPicker></ngx-mat-datetime-picker>
|
||||||
|
<mat-error>
|
||||||
|
{{'jitsi.rooms.error.expires' | i18n}}
|
||||||
|
</mat-error>
|
||||||
|
</mat-form-field>
|
||||||
|
|
||||||
|
<mat-form-field>
|
||||||
|
<input matInput [ngxMatDatetimePicker]="startsPicker" [(ngModel)]="jitsiRoom.starts" formControlName="starts"
|
||||||
|
placeholder="{{'jitsi.rooms.starts' | i18n}}">
|
||||||
|
<mat-datepicker-toggle matSuffix [for]="startsPicker"></mat-datepicker-toggle>
|
||||||
|
<ngx-mat-datetime-picker #startsPicker></ngx-mat-datetime-picker>
|
||||||
|
<mat-error>
|
||||||
|
{{'jitsi.rooms.error.starts' | i18n}}
|
||||||
|
</mat-error>
|
||||||
|
</mat-form-field>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</mat-card-content>
|
||||||
|
<mat-card-actions>
|
||||||
|
<button *ngIf="jitsiRoomsQuota && !working" mat-raised-button color="primary" [disabled]="form.invalid">
|
||||||
|
{{'jitsi.rooms.create' | i18n}}
|
||||||
|
</button>
|
||||||
|
</mat-card-actions>
|
||||||
|
</mat-card>
|
||||||
|
|
||||||
|
</form>
|
25
src/app/pages/jitsi/jitsi.component.scss
Normal file
25
src/app/pages/jitsi/jitsi.component.scss
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
mat-form-field {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mat-header-cell,
|
||||||
|
.mat-cell {
|
||||||
|
&.text-right {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.align-right{
|
||||||
|
display: flex;
|
||||||
|
padding: 21px 0;
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
.url {
|
||||||
|
display: block;
|
||||||
|
width: 200px;
|
||||||
|
max-width: 200px;
|
||||||
|
white-space: nowrap;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
25
src/app/pages/jitsi/jitsi.component.spec.ts
Normal file
25
src/app/pages/jitsi/jitsi.component.spec.ts
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { JitsiComponent } from './jitsi.component';
|
||||||
|
|
||||||
|
describe('JitsiComponent', () => {
|
||||||
|
let component: JitsiComponent;
|
||||||
|
let fixture: ComponentFixture<JitsiComponent>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
declarations: [ JitsiComponent ]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(JitsiComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
165
src/app/pages/jitsi/jitsi.component.ts
Normal file
165
src/app/pages/jitsi/jitsi.component.ts
Normal file
@ -0,0 +1,165 @@
|
|||||||
|
import {Component, OnInit, ViewChild, Inject} from '@angular/core';
|
||||||
|
import {MatSnackBar} from '@angular/material/snack-bar';
|
||||||
|
import {Sort} from '@angular/material/sort';
|
||||||
|
import {FormBuilder, FormGroup, Validators, NgForm} from '@angular/forms';
|
||||||
|
import {MatDialog, MatDialogRef, MAT_DIALOG_DATA} from '@angular/material/dialog';
|
||||||
|
|
||||||
|
import {QuotaService} from '../../services/quota.service';
|
||||||
|
import {JitsiService} from '../../services/jitsi.service';
|
||||||
|
import {ConfirmDialog} from '../../ui/confirm/confirm.component';
|
||||||
|
import {I18nService} from './../../services/i18n.service';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-account-jitsi',
|
||||||
|
templateUrl: './jitsi.component.html',
|
||||||
|
styleUrls: ['./jitsi.component.scss']
|
||||||
|
})
|
||||||
|
export class JitsiComponent implements OnInit {
|
||||||
|
|
||||||
|
form: FormGroup;
|
||||||
|
@ViewChild('formDirective') private formDirective: NgForm;
|
||||||
|
jitsiRoomsQuota: number = 0;
|
||||||
|
jitsiRooms: any[] = [];
|
||||||
|
jitsiRoom: any = {};
|
||||||
|
success: boolean;
|
||||||
|
working: boolean;
|
||||||
|
datetimeformat: String;
|
||||||
|
|
||||||
|
jitsiRoomsColumns = ["room", "starts", "expires", "moderationUrl", "delete"];
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private quotaService: QuotaService,
|
||||||
|
private formBuilder: FormBuilder,
|
||||||
|
private jitsiService: JitsiService,
|
||||||
|
private i18n: I18nService,
|
||||||
|
public dialog: MatDialog) {}
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.datetimeformat = this.i18n.get('format.datetime', []);
|
||||||
|
|
||||||
|
this.form = this.formBuilder.group({
|
||||||
|
room: ['', Validators.required],
|
||||||
|
starts: ['', Validators.nullValidator],
|
||||||
|
expires: ['', Validators.required],
|
||||||
|
});
|
||||||
|
|
||||||
|
this.update();
|
||||||
|
}
|
||||||
|
|
||||||
|
create(): void {
|
||||||
|
this.working = true;
|
||||||
|
this.jitsiService.create(this.jitsiRoom).subscribe(response => {
|
||||||
|
this.update();
|
||||||
|
this.formDirective.resetForm();
|
||||||
|
this.jitsiRoom = {};
|
||||||
|
this.working = false;
|
||||||
|
}, (error) => {
|
||||||
|
this.working = false;
|
||||||
|
if(error.status == 409) {
|
||||||
|
let errors = {};
|
||||||
|
for(let code of error.error) {
|
||||||
|
errors[code.field] = errors[code.field] || {};
|
||||||
|
errors[code.field][code.code] = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
for(let code in errors) {
|
||||||
|
this.form.get(code).setErrors(errors[code]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
update() {
|
||||||
|
this.jitsiRoomsQuota = 0;
|
||||||
|
this.quotaService.quotas().subscribe((data: any) => {
|
||||||
|
for(let quota of data) {
|
||||||
|
if(quota.name == "jitsi") {
|
||||||
|
this.jitsiRoomsQuota = quota.value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
this.jitsiService.get().subscribe((data: any) => {
|
||||||
|
this.jitsiRooms = data;
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
confirmDelete(jitsiRoom) {
|
||||||
|
const dialogRef = this.dialog.open(ConfirmDialog, {
|
||||||
|
data: {
|
||||||
|
'label': 'jitsi.rooms.confirmDelete',
|
||||||
|
'args': [jitsiRoom.room]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
dialogRef.afterClosed().subscribe(result => {
|
||||||
|
if(result) {
|
||||||
|
this.jitsiService.delete(jitsiRoom.id).subscribe((result: any) => {
|
||||||
|
this.update();
|
||||||
|
})
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
sortData(sort: Sort) {
|
||||||
|
const data = this.jitsiRooms.slice();
|
||||||
|
if(!sort.active || sort.direction === '') {
|
||||||
|
this.jitsiRooms = data;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.jitsiRooms = data.sort((a, b) => {
|
||||||
|
const isAsc = sort.direction === 'asc';
|
||||||
|
switch(sort.active) {
|
||||||
|
case 'room': return this.compare(a.room, b.room, isAsc);
|
||||||
|
case 'starts': return this.compare(a.room, b.room, isAsc);
|
||||||
|
case 'expires': return this.compare(a.room, b.room, isAsc);
|
||||||
|
default: return 0;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
compare(a: number | string | String, b: number | string | String, isAsc: boolean) {
|
||||||
|
return (a < b ? -1 : 1) * (isAsc ? 1 : -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
share(jitsiRoom) {
|
||||||
|
const dialogRef = this.dialog.open(JitsiShareDialog, {
|
||||||
|
data: jitsiRoom,
|
||||||
|
minWidth: '300px',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-jitsi-share-dialog',
|
||||||
|
templateUrl: 'jitsi.share.html',
|
||||||
|
styleUrls: ['./jitsi.share.scss']
|
||||||
|
})
|
||||||
|
export class JitsiShareDialog {
|
||||||
|
|
||||||
|
jitsiRoom: any;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private i18n: I18nService,
|
||||||
|
private snackBar: MatSnackBar,
|
||||||
|
public dialogRef: MatDialogRef<JitsiShareDialog>,
|
||||||
|
@Inject(MAT_DIALOG_DATA) public data: any) {
|
||||||
|
this.jitsiRoom = data;
|
||||||
|
}
|
||||||
|
|
||||||
|
copyToClipboard(jitsiRoom: any) {
|
||||||
|
const selBox = document.createElement('textarea');
|
||||||
|
selBox.value = jitsiRoom.url;
|
||||||
|
document.body.appendChild(selBox);
|
||||||
|
selBox.focus();
|
||||||
|
selBox.select();
|
||||||
|
document.execCommand('copy');
|
||||||
|
document.body.removeChild(selBox);
|
||||||
|
this.snackBar.open(this.i18n.get("jitsi.share.clipboard.copied", []), this.i18n.get("close", []), {
|
||||||
|
duration: 3000
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
20
src/app/pages/jitsi/jitsi.share.html
Normal file
20
src/app/pages/jitsi/jitsi.share.html
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
<h1 mat-dialog-title><mat-icon inline="true">share</mat-icon> {{'jitsi.share' | i18n}}</h1>
|
||||||
|
<div mat-dialog-content>
|
||||||
|
<mat-list>
|
||||||
|
<mat-list-item>
|
||||||
|
<a mat-raised-button color="accent"
|
||||||
|
href="mailto:?&subject={{'jitsi.share.email.subject' | i18n:jitsiRoom.room}}&body={{jitsiRoom.url}}">
|
||||||
|
<mat-icon>mail</mat-icon> {{'jitsi.share.email' | i18n}}
|
||||||
|
</a>
|
||||||
|
</mat-list-item>
|
||||||
|
|
||||||
|
<mat-list-item>
|
||||||
|
<a mat-raised-button color="accent" (click)="copyToClipboard(jitsiRoom)">
|
||||||
|
<mat-icon>content_paste</mat-icon> {{'jitsi.share.clipboard' | i18n}}
|
||||||
|
</a>
|
||||||
|
</mat-list-item>
|
||||||
|
</mat-list>
|
||||||
|
</div>
|
||||||
|
<div mat-dialog-actions>
|
||||||
|
<button mat-button mat-dialog-close>{{'close' | i18n}}</button>
|
||||||
|
</div>
|
4
src/app/pages/jitsi/jitsi.share.scss
Normal file
4
src/app/pages/jitsi/jitsi.share.scss
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
.mat-raised-button {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
}
|
@ -18,7 +18,11 @@ export class LoginComponent implements OnInit {
|
|||||||
targetRoute = '/services';
|
targetRoute = '/services';
|
||||||
loginModel = {};
|
loginModel = {};
|
||||||
|
|
||||||
constructor(private formBuilder: FormBuilder, private authService: AuthService, private router: Router, private route: ActivatedRoute) { }
|
constructor(
|
||||||
|
private formBuilder: FormBuilder,
|
||||||
|
private authService: AuthService,
|
||||||
|
private router: Router,
|
||||||
|
private route: ActivatedRoute) {}
|
||||||
|
|
||||||
async ngOnInit() {
|
async ngOnInit() {
|
||||||
this.form = this.formBuilder.group({
|
this.form = this.formBuilder.group({
|
||||||
@ -30,6 +34,7 @@ export class LoginComponent implements OnInit {
|
|||||||
this.route.queryParams.subscribe(params => {
|
this.route.queryParams.subscribe(params => {
|
||||||
if(params['target']) {
|
if(params['target']) {
|
||||||
this.targetRoute = params['target'];
|
this.targetRoute = params['target'];
|
||||||
|
this.router.navigate([], {queryParams: {target: null}, queryParamsHandling: 'merge'});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -16,8 +16,9 @@
|
|||||||
</p>
|
</p>
|
||||||
</mat-card-content>
|
</mat-card-content>
|
||||||
<mat-card-actions>
|
<mat-card-actions>
|
||||||
<a href="{{service.url}}" target="_blank" mat-raised-button color="primary">{{'services.goto' |
|
<a href="{{service.url}}" [target]="service.sameSite ? '_self' : '_blank'" mat-raised-button
|
||||||
i18n}}</a>
|
color="accent">{{'services.goto' | i18n}} <mat-icon *ngIf="!service.sameSite" inline="true">
|
||||||
|
open_in_new</mat-icon></a>
|
||||||
</mat-card-actions>
|
</mat-card-actions>
|
||||||
</mat-card>
|
</mat-card>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import {Component, OnInit} from '@angular/core';
|
import {Component, OnInit} from '@angular/core';
|
||||||
import {Location} from '@angular/common'
|
import {Location} from '@angular/common'
|
||||||
|
import {Router, ActivatedRoute} from '@angular/router';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-unavailable',
|
selector: 'app-unavailable',
|
||||||
@ -7,14 +8,29 @@ import { Location } from '@angular/common'
|
|||||||
})
|
})
|
||||||
export class UnavailableComponent implements OnInit {
|
export class UnavailableComponent implements OnInit {
|
||||||
|
|
||||||
|
targetRoute = '';
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private location: Location) {}
|
private location: Location,
|
||||||
|
private router: Router,
|
||||||
|
private route: ActivatedRoute) {}
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
|
this.route.queryParams.subscribe(params => {
|
||||||
|
if(params['target']) {
|
||||||
|
this.targetRoute = params['target'];
|
||||||
|
this.router.navigate([], {queryParams: {target: null}, queryParamsHandling: 'merge'});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
retry() {
|
retry() {
|
||||||
this.location.back();
|
if(!this.targetRoute || this.targetRoute === "unavailable" || this.targetRoute === "/unavailable") {
|
||||||
|
this.location.back;
|
||||||
|
} else {
|
||||||
|
this.router.navigate([this.targetRoute]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -71,9 +71,12 @@ export class I18nService {
|
|||||||
if(!from) {
|
if(!from) {
|
||||||
return key;
|
return key;
|
||||||
} else if(from[key]) {
|
} else if(from[key]) {
|
||||||
|
if(typeof from[key] === 'object') {
|
||||||
if(from[key]["."]) {
|
if(from[key]["."]) {
|
||||||
return this.insertArguments(from[key]["."], args);
|
return this.insertArguments(from[key]["."], args);
|
||||||
}
|
}
|
||||||
|
return key;
|
||||||
|
}
|
||||||
return this.insertArguments(from[key], args);
|
return this.insertArguments(from[key], args);
|
||||||
} else {
|
} else {
|
||||||
let keys = key.split(".");
|
let keys = key.split(".");
|
||||||
|
26
src/app/services/jitsi.service.ts
Normal file
26
src/app/services/jitsi.service.ts
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
import {Injectable} from '@angular/core';
|
||||||
|
import {HttpClient} from '@angular/common/http';
|
||||||
|
|
||||||
|
import {environment} from '../../environments/environment';
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root',
|
||||||
|
})
|
||||||
|
export class JitsiService {
|
||||||
|
|
||||||
|
constructor(private http: HttpClient) {
|
||||||
|
}
|
||||||
|
|
||||||
|
get() {
|
||||||
|
return this.http.get(environment.apiUrl + "/jitsi/rooms");
|
||||||
|
}
|
||||||
|
|
||||||
|
create(jitsiRoom) {
|
||||||
|
return this.http.post(environment.apiUrl + "/jitsi/rooms", jitsiRoom);
|
||||||
|
}
|
||||||
|
|
||||||
|
delete(id) {
|
||||||
|
return this.http.delete(environment.apiUrl + "/jitsi/rooms/" + id);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -16,7 +16,7 @@ export class PermissionsComponent implements OnInit {
|
|||||||
constructor(private i18n: I18nService) {}
|
constructor(private i18n: I18nService) {}
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.datetimeformat = this.i18n.get('date-time-format', []);
|
this.datetimeformat = this.i18n.get('format.datetime', []);
|
||||||
}
|
}
|
||||||
|
|
||||||
sortData(sort: Sort) {
|
sortData(sort: Sort) {
|
||||||
|
@ -29,14 +29,23 @@
|
|||||||
</mat-error>
|
</mat-error>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
<mat-form-field *ngSwitchCase="'DATE'">
|
<mat-form-field *ngSwitchCase="'DATE'">
|
||||||
<input matInput [matDatepicker]="picker" [(ngModel)]="profileField.value" formControlName="value"
|
<input matInput [matDatepicker]="datePicker" [(ngModel)]="profileField.value" formControlName="value"
|
||||||
placeholder="{{'profileField.value' | i18n}}">
|
placeholder="{{'profileField.value' | i18n}}">
|
||||||
<mat-datepicker-toggle matSuffix [for]="picker"></mat-datepicker-toggle>
|
<mat-datepicker-toggle matSuffix [for]="datePicker"></mat-datepicker-toggle>
|
||||||
<mat-datepicker #picker></mat-datepicker>
|
<mat-datepicker #datePicker></mat-datepicker>
|
||||||
<mat-error>
|
<mat-error>
|
||||||
{{'profileField.error.DATE' | i18n}}
|
{{'profileField.error.DATE' | i18n}}
|
||||||
</mat-error>
|
</mat-error>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
|
<mat-form-field *ngSwitchCase="'DATETIME'">
|
||||||
|
<input matInput [ngxMatDatetimePicker]="datetimePicker" [(ngModel)]="profileField.value" formControlName="value"
|
||||||
|
placeholder="{{'profileField.value' | i18n}}">
|
||||||
|
<mat-datepicker-toggle matSuffix [for]="datetimePicker"></mat-datepicker-toggle>
|
||||||
|
<ngx-mat-datetime-picker #datetimePicker></ngx-mat-datetime-picker>
|
||||||
|
<mat-error>
|
||||||
|
{{'profileField.error.DATETIME' | i18n}}
|
||||||
|
</mat-error>
|
||||||
|
</mat-form-field>
|
||||||
<mat-form-field *ngSwitchCase="'URL'">
|
<mat-form-field *ngSwitchCase="'URL'">
|
||||||
<input matInput type="url" [(ngModel)]="profileField.value" formControlName="value"
|
<input matInput type="url" [(ngModel)]="profileField.value" formControlName="value"
|
||||||
placeholder="{{'profileField.value' | i18n}}">
|
placeholder="{{'profileField.value' | i18n}}">
|
||||||
@ -81,7 +90,8 @@
|
|||||||
</mat-option>
|
</mat-option>
|
||||||
|
|
||||||
<mat-select-trigger>
|
<mat-select-trigger>
|
||||||
<mat-icon inline="true">{{'visibility.' + profileField.visibility + '.icon' | i18n}}</mat-icon> {{'visibility.' +
|
<mat-icon inline="true">{{'visibility.' + profileField.visibility + '.icon' | i18n}}</mat-icon>
|
||||||
|
{{'visibility.' +
|
||||||
profileField.visibility | i18n}}
|
profileField.visibility | i18n}}
|
||||||
</mat-select-trigger>
|
</mat-select-trigger>
|
||||||
</mat-select>
|
</mat-select>
|
||||||
|
@ -11,11 +11,14 @@
|
|||||||
<td mat-cell *matCellDef="let profileField">
|
<td mat-cell *matCellDef="let profileField">
|
||||||
<div [ngSwitch]="profileField.type">
|
<div [ngSwitch]="profileField.type">
|
||||||
<span *ngSwitchCase="'TEXT'">{{profileField.value}}</span>
|
<span *ngSwitchCase="'TEXT'">{{profileField.value}}</span>
|
||||||
<span *ngSwitchCase="'DATE'">{{profileField.value | date:datetimeformat}}</span>
|
<span *ngSwitchCase="'DATE'">{{profileField.value | date:dateformat}}</span>
|
||||||
<a *ngSwitchCase="'URL'" href="{{profileField.value}}">{{profileField.value}}</a>
|
<span *ngSwitchCase="'DATETIME'">{{profileField.value | date:datetimeformat}}</span>
|
||||||
<a *ngSwitchCase="'EMAIL'" href="mailto:{{profileField.value}}">{{profileField.value}}</a>
|
<span *ngSwitchCase="'TIME'">{{profileField.value | date:timeformat}}</span>
|
||||||
|
<a *ngSwitchCase="'URL'" class="accent" href="{{profileField.value}}">{{profileField.value}}</a>
|
||||||
|
<a *ngSwitchCase="'EMAIL'" class="accent"
|
||||||
|
href="mailto:{{profileField.value}}">{{profileField.value}}</a>
|
||||||
<span *ngSwitchCase="'NUMBER'">{{profileField.value}}</span>
|
<span *ngSwitchCase="'NUMBER'">{{profileField.value}}</span>
|
||||||
<button *ngSwitchCase="'BLOB'" mat-raised-button
|
<button *ngSwitchCase="'BLOB'" mat-raised-buttonu
|
||||||
(click)="openBlob(profileField)">{{'profileField.openBlob' | i18n}}</button>
|
(click)="openBlob(profileField)">{{'profileField.openBlob' | i18n}}</button>
|
||||||
<mat-slide-toggle *ngSwitchCase="'BOOL'" [checked]="profileField.value == 'true'" disabled>
|
<mat-slide-toggle *ngSwitchCase="'BOOL'" [checked]="profileField.value == 'true'" disabled>
|
||||||
</mat-slide-toggle>
|
</mat-slide-toggle>
|
||||||
@ -25,7 +28,9 @@
|
|||||||
|
|
||||||
<ng-container matColumnDef="visibility" *ngIf="edit">
|
<ng-container matColumnDef="visibility" *ngIf="edit">
|
||||||
<th mat-header-cell *matHeaderCellDef mat-sort-header="visibility"> {{'visibility' | i18n}} </th>
|
<th mat-header-cell *matHeaderCellDef mat-sort-header="visibility"> {{'visibility' | i18n}} </th>
|
||||||
<td mat-cell *matCellDef="let profileField"> <mat-icon inline="true">{{'visibility.' + profileField.visibility + '.icon' | i18n}}</mat-icon> {{'visibility.' + profileField.visibility | i18n}}
|
<td mat-cell *matCellDef="let profileField">
|
||||||
|
<mat-icon inline="true">{{'visibility.' + profileField.visibility + '.icon' | i18n}}</mat-icon>
|
||||||
|
{{'visibility.' + profileField.visibility | i18n}}
|
||||||
</td>
|
</td>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
|
@ -17,6 +17,9 @@ export class ProfileFieldsComponent implements OnInit {
|
|||||||
@Input() edit;
|
@Input() edit;
|
||||||
profileFieldColumns = ["name", "value"];
|
profileFieldColumns = ["name", "value"];
|
||||||
profileFields: Array<any> = [];
|
profileFields: Array<any> = [];
|
||||||
|
datetimeformat: String;
|
||||||
|
dateformat: String;
|
||||||
|
timeformat: String;
|
||||||
|
|
||||||
constructor(private i18n: I18nService, private profileService: ProfileService, public dialog: MatDialog) {}
|
constructor(private i18n: I18nService, private profileService: ProfileService, public dialog: MatDialog) {}
|
||||||
|
|
||||||
@ -27,6 +30,11 @@ export class ProfileFieldsComponent implements OnInit {
|
|||||||
this.profileFieldColumns.push("delete");
|
this.profileFieldColumns.push("delete");
|
||||||
}
|
}
|
||||||
this.update();
|
this.update();
|
||||||
|
|
||||||
|
|
||||||
|
this.dateformat = this.i18n.get('format.date', []);
|
||||||
|
this.datetimeformat = this.i18n.get('format.datetime', []);
|
||||||
|
this.timeformat = this.i18n.get('format.time', []);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -140,7 +148,7 @@ export class ProfileFieldDialog {
|
|||||||
form: FormGroup;
|
form: FormGroup;
|
||||||
profileField;
|
profileField;
|
||||||
|
|
||||||
types = ["TEXT", "NUMBER", "DATE", "URL", "EMAIL", "BOOL", "BLOB"];
|
types = ["TEXT", "NUMBER", "DATE", "DATETIME", "URL", "EMAIL", "BOOL", "BLOB"];
|
||||||
visibilities = ["PRIVATE", "PROTECTED", "PUBLIC"];
|
visibilities = ["PRIVATE", "PROTECTED", "PUBLIC"];
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
|
@ -8,7 +8,6 @@
|
|||||||
"cancel": "Abbrechen",
|
"cancel": "Abbrechen",
|
||||||
"close": "Schließen",
|
"close": "Schließen",
|
||||||
"confirm": "Bestätigen",
|
"confirm": "Bestätigen",
|
||||||
"date-time-format": "dd.MM.yyyy HH:mm:ss",
|
|
||||||
"email": {
|
"email": {
|
||||||
".": "E-Mail Adresse",
|
".": "E-Mail Adresse",
|
||||||
"invalid": "ungültige E-Mail Adresse",
|
"invalid": "ungültige E-Mail Adresse",
|
||||||
@ -17,12 +16,48 @@
|
|||||||
"hint": "Eine primäre E-Mail Adresse dient dazu eine andere Kontaktmöglichkeit als deine we.bstly Adresse anzugeben."
|
"hint": "Eine primäre E-Mail Adresse dient dazu eine andere Kontaktmöglichkeit als deine we.bstly Adresse anzugeben."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"format": {
|
||||||
|
"date": "dd.MM.yyyy",
|
||||||
|
"datetime": "dd.MM.yyyy HH:mm:ss",
|
||||||
|
"time": "HH:mm:ss"
|
||||||
|
},
|
||||||
"greet": "Hallo {0}",
|
"greet": "Hallo {0}",
|
||||||
"help": "Hilfe",
|
"help": "Hilfe",
|
||||||
"imprint": "Impressum",
|
"imprint": "Impressum",
|
||||||
"info": {
|
"info": {
|
||||||
".": "Info"
|
".": "Info"
|
||||||
},
|
},
|
||||||
|
"jitsi": {
|
||||||
|
"rooms": {
|
||||||
|
".": "Jitsi Räume",
|
||||||
|
"confirmDelete": "Möchtest du wirklich deinen Jitsi Raum '{0}' löschen?",
|
||||||
|
"create": "Jitsi Raum erstellen",
|
||||||
|
"delete": "Löschen",
|
||||||
|
"error": {
|
||||||
|
"expires": "Ungültiges Ende.",
|
||||||
|
"room": "Bitte wähle einen anderen Namen für den Raum. Erlaubt sind nur Buchstaben und Zahlen.",
|
||||||
|
"starts": "Ungültiger Beginn."
|
||||||
|
},
|
||||||
|
"expires": "Ende",
|
||||||
|
"info": "Du kannst hier Jitsi Räume erstellen. Die Anzahl wird über eine Quota begrenzt.",
|
||||||
|
"left": "Du kannst noch {0} Jitsi Räume erstellen.",
|
||||||
|
"moderationUrl": "Url für Moderation",
|
||||||
|
"noQuota": "Deine Quota für Jitsi Räume ist leider aufgebraucht.",
|
||||||
|
"room": "Name",
|
||||||
|
"starts": "Beginn"
|
||||||
|
},
|
||||||
|
"share": {
|
||||||
|
".": "Teilen",
|
||||||
|
"clipboard": {
|
||||||
|
".": "In Zwischenablage kopieren",
|
||||||
|
"copied": "In die Zwischenablage kopiert"
|
||||||
|
},
|
||||||
|
"email": {
|
||||||
|
".": "Via E-Mail teilen",
|
||||||
|
"subject": "Einladung in Videokonferenz {0}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"locale": {
|
"locale": {
|
||||||
"de-informal": {
|
"de-informal": {
|
||||||
"long": "Deutsch",
|
"long": "Deutsch",
|
||||||
@ -57,7 +92,8 @@
|
|||||||
"INSUFFICIENT_LOWERCASE": "Bitte mindestens einen Kleinbuchstaben eingeben.",
|
"INSUFFICIENT_LOWERCASE": "Bitte mindestens einen Kleinbuchstaben eingeben.",
|
||||||
"INSUFFICIENT_SPECIAL": "Bitte mindestens ein Sonderzeichen eingeben.",
|
"INSUFFICIENT_SPECIAL": "Bitte mindestens ein Sonderzeichen eingeben.",
|
||||||
"INSUFFICIENT_UPPERCASE": "Bitte mindestens einen Großbuchstaben eingeben.",
|
"INSUFFICIENT_UPPERCASE": "Bitte mindestens einen Großbuchstaben eingeben.",
|
||||||
"TOO_SHORT": "Bitte ein längeres Passwort wählen."
|
"TOO_SHORT": "Bitte ein längeres Passwort wählen.",
|
||||||
|
"NOT_MATCH": "Passwörter stimmen nicht überein."
|
||||||
},
|
},
|
||||||
"forgot": "Passwort vergessen",
|
"forgot": "Passwort vergessen",
|
||||||
"invalid": {
|
"invalid": {
|
||||||
@ -109,6 +145,9 @@
|
|||||||
"DATE": {
|
"DATE": {
|
||||||
".": "Kein gültiges Datum"
|
".": "Kein gültiges Datum"
|
||||||
},
|
},
|
||||||
|
"DATETIME": {
|
||||||
|
".": "Kein gültiges Datum oder Uhrzeit"
|
||||||
|
},
|
||||||
"EMAIL": {
|
"EMAIL": {
|
||||||
".": "Keine gültige E-Mail Adresse"
|
".": "Keine gültige E-Mail Adresse"
|
||||||
},
|
},
|
||||||
@ -119,6 +158,9 @@
|
|||||||
"TEXT": {
|
"TEXT": {
|
||||||
".": "Textfeld zu lang"
|
".": "Textfeld zu lang"
|
||||||
},
|
},
|
||||||
|
"TIME": {
|
||||||
|
".": "Keine gültige Uhrzeit"
|
||||||
|
},
|
||||||
"type": "Ungültiger Typ für dieses Profilfeld",
|
"type": "Ungültiger Typ für dieses Profilfeld",
|
||||||
"URL": {
|
"URL": {
|
||||||
".": "Keine gültige URL"
|
".": "Keine gültige URL"
|
||||||
@ -147,6 +189,9 @@
|
|||||||
"DATE": {
|
"DATE": {
|
||||||
".": "Datum"
|
".": "Datum"
|
||||||
},
|
},
|
||||||
|
"DATETIME": {
|
||||||
|
".": "Datum mit Uhrzeit"
|
||||||
|
},
|
||||||
"EMAIL": {
|
"EMAIL": {
|
||||||
".": "E-Mail"
|
".": "E-Mail"
|
||||||
},
|
},
|
||||||
@ -156,6 +201,9 @@
|
|||||||
"TEXT": {
|
"TEXT": {
|
||||||
".": "Textfeld"
|
".": "Textfeld"
|
||||||
},
|
},
|
||||||
|
"TIME": {
|
||||||
|
".": "Uhrzeit"
|
||||||
|
},
|
||||||
"URL": {
|
"URL": {
|
||||||
".": "URL"
|
".": "URL"
|
||||||
}
|
}
|
||||||
@ -243,6 +291,12 @@
|
|||||||
"title": "Gitea"
|
"title": "Gitea"
|
||||||
},
|
},
|
||||||
"goto": "Zum Dienst",
|
"goto": "Zum Dienst",
|
||||||
|
"jitsi": {
|
||||||
|
"icon": "video_call",
|
||||||
|
"subtitle": "Video Konferenzen",
|
||||||
|
"text": "Video Konferenzen mit allen Funktionen für Online-Treffen mit Video und Audio Streams.",
|
||||||
|
"title": "Jitsi Meet"
|
||||||
|
},
|
||||||
"mail": {
|
"mail": {
|
||||||
"icon": "email",
|
"icon": "email",
|
||||||
"subtitle": "E-Mail Konto",
|
"subtitle": "E-Mail Konto",
|
||||||
@ -261,6 +315,12 @@
|
|||||||
"text": "Dateiverwaltung, Kalender, Aufgabenmanagement, Kontaktmanagement, Abstimmungen und mehr.",
|
"text": "Dateiverwaltung, Kalender, Aufgabenmanagement, Kontaktmanagement, Abstimmungen und mehr.",
|
||||||
"title": "Nextcloud"
|
"title": "Nextcloud"
|
||||||
},
|
},
|
||||||
|
"owncast": {
|
||||||
|
"icon": "videocam",
|
||||||
|
"subtitle": "Livestreams",
|
||||||
|
"text": "Livestreams von Bastelei e. V.",
|
||||||
|
"title": "Owncast"
|
||||||
|
},
|
||||||
"partey": {
|
"partey": {
|
||||||
"icon": "celebration",
|
"icon": "celebration",
|
||||||
"subtitle": "Virtuelles Vereinsheim",
|
"subtitle": "Virtuelles Vereinsheim",
|
||||||
|
@ -8,7 +8,6 @@
|
|||||||
"cancel": "Cancel",
|
"cancel": "Cancel",
|
||||||
"close": "Close",
|
"close": "Close",
|
||||||
"confirm": "Confirm",
|
"confirm": "Confirm",
|
||||||
"date-time-format": "MM/dd/yyy h:mm:ss a",
|
|
||||||
"email": {
|
"email": {
|
||||||
".": "Email address",
|
".": "Email address",
|
||||||
"invalid": "invalid email address",
|
"invalid": "invalid email address",
|
||||||
@ -17,12 +16,48 @@
|
|||||||
"hint": "A primary email address is used for contact you instead of you we.bstly address."
|
"hint": "A primary email address is used for contact you instead of you we.bstly address."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"format": {
|
||||||
|
"date": "MM/dd/yyy",
|
||||||
|
"datetime": "MM/dd/yyy h:mm:ss a",
|
||||||
|
"time": "h:mm:ss a"
|
||||||
|
},
|
||||||
"greet": "Hello {0}",
|
"greet": "Hello {0}",
|
||||||
"help": "Help",
|
"help": "Help",
|
||||||
"imprint": "Imprint",
|
"imprint": "Imprint",
|
||||||
"info": {
|
"info": {
|
||||||
".": "Info"
|
".": "Info"
|
||||||
},
|
},
|
||||||
|
"jitsi": {
|
||||||
|
"rooms": {
|
||||||
|
".": "Jitsi Rooms",
|
||||||
|
"confirmDelete": "Are you sure you want to delete your Jitsi Room '{0}'?",
|
||||||
|
"create": "Create Jitsi Room",
|
||||||
|
"delete": "Delete",
|
||||||
|
"error": {
|
||||||
|
"expires": "Invalid expiry.",
|
||||||
|
"room": "Please choose another name for the room. Only letters and numbers are allowed",
|
||||||
|
"starts": "Invalid start."
|
||||||
|
},
|
||||||
|
"expires": "Expires",
|
||||||
|
"info": "You can create new Jitsi Rooms here. The number is limited due to a quota.",
|
||||||
|
"left": "You have {0} Jitsi Room(s) left.",
|
||||||
|
"moderationUrl": "Moderation url",
|
||||||
|
"noQuota": "Your quota for Jitsi Rooms is depleted.",
|
||||||
|
"room": "Name",
|
||||||
|
"starts": "Starts"
|
||||||
|
},
|
||||||
|
"share" : {
|
||||||
|
"." : "Share",
|
||||||
|
"clipboard" : {
|
||||||
|
"." : "Copy to clipboard",
|
||||||
|
"copied": "Copied to clipboard"
|
||||||
|
},
|
||||||
|
"email" : {
|
||||||
|
"." : "Share via email",
|
||||||
|
"subject" : "Invite to videoconference {0}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"locale": {
|
"locale": {
|
||||||
"de-informal": {
|
"de-informal": {
|
||||||
"long": "Deutsch",
|
"long": "Deutsch",
|
||||||
@ -243,6 +278,12 @@
|
|||||||
"title": "Gitea"
|
"title": "Gitea"
|
||||||
},
|
},
|
||||||
"goto": "To service",
|
"goto": "To service",
|
||||||
|
"jitsi" : {
|
||||||
|
"icon": "video_call",
|
||||||
|
"subtitle": "Video conferencing",
|
||||||
|
"text": "Video conferencing with all functionality needed for meeting online with Video and Audio streams.",
|
||||||
|
"title": "Jitsi Meet"
|
||||||
|
},
|
||||||
"mail": {
|
"mail": {
|
||||||
"icon": "email",
|
"icon": "email",
|
||||||
"subtitle": "Email Account",
|
"subtitle": "Email Account",
|
||||||
@ -261,6 +302,12 @@
|
|||||||
"text": "File management, calendar, tasks, contacts, polls and more.",
|
"text": "File management, calendar, tasks, contacts, polls and more.",
|
||||||
"title": "Nextcloud"
|
"title": "Nextcloud"
|
||||||
},
|
},
|
||||||
|
"owncast": {
|
||||||
|
"icon": "videocam",
|
||||||
|
"subtitle": "Livestreams",
|
||||||
|
"text": "Livestreams of Bastelei e. V.",
|
||||||
|
"title": "Owncast"
|
||||||
|
},
|
||||||
"partey": {
|
"partey": {
|
||||||
"icon": "celebration",
|
"icon": "celebration",
|
||||||
"subtitle": "Virtual clubhouse",
|
"subtitle": "Virtual clubhouse",
|
||||||
|
@ -903,7 +903,7 @@
|
|||||||
|
|
||||||
<h4 id="Rechenzentrum">Rechenzentrum</h4>
|
<h4 id="Rechenzentrum">Rechenzentrum</h4>
|
||||||
|
|
||||||
<p>Die Daten werden im Rechenzentrum der <a href="https://www.netcup.de/ueber-netcup/rechenzentrum.php"
|
<p>Die Daten werden im Rechenzentrum der <a class="accent" href="https://www.netcup.de/ueber-netcup/rechenzentrum.php"
|
||||||
target="_blank">netcup GmbH</a> gespeichert. Eine regelmäßige, automatisierte Datensicherung der
|
target="_blank">netcup GmbH</a> gespeichert. Eine regelmäßige, automatisierte Datensicherung der
|
||||||
Bestandsdaten
|
Bestandsdaten
|
||||||
wird durchgeführt.
|
wird durchgeführt.
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
<h2>Nutzungsbedingungen</h2>
|
<h2>Nutzungsbedingungen</h2>
|
||||||
|
|
||||||
<h3>Bereitstellung</h3>
|
<h3>Bereitstellung</h3>
|
||||||
<p>Mit we.bstly stellt der <a href="https://www.bstly.de" target="_blank">Bastelei e. V.</a> eine Plattform
|
<p>Mit we.bstly stellt der <a class="accent" href="https://www.bstly.de" target="_blank">Bastelei e. V.</a> eine Plattform
|
||||||
bereit, um verschiedene digitale Services zeitlich befristet zur Verfügung zu stellen. </p>
|
bereit, um verschiedene digitale Services zeitlich befristet zur Verfügung zu stellen. </p>
|
||||||
<p>we.bstly und damit verbundene Services stehen allen Vereinsmitgliedern des Bastelei e. V. sowie allen
|
<p>we.bstly und damit verbundene Services stehen allen Vereinsmitgliedern des Bastelei e. V. sowie allen
|
||||||
NutzerInnen, die die entsprechenden Services direkt gebucht haben, zur Verfügung.</p>
|
NutzerInnen, die die entsprechenden Services direkt gebucht haben, zur Verfügung.</p>
|
||||||
@ -21,7 +21,7 @@
|
|||||||
|
|
||||||
<h3>Datensicherheit</h3>
|
<h3>Datensicherheit</h3>
|
||||||
<p>we.bstly und damit verbundene Services haben Sicherheitsmerkmale implementiert, die Schutz vor Verlust, Missbrauch
|
<p>we.bstly und damit verbundene Services haben Sicherheitsmerkmale implementiert, die Schutz vor Verlust, Missbrauch
|
||||||
und Manipulation der Daten und Dateien bieten. Dennoch kann ein 100% Schutz nicht gewährleistet werden (siehe <a
|
und Manipulation der Daten und Dateien bieten. Dennoch kann ein 100% Schutz nicht gewährleistet werden (siehe <a class="accent"
|
||||||
href="/terms-of-service#availability">Verfügbarkeit und Leistungsstörungen</a>).</p>
|
href="/terms-of-service#availability">Verfügbarkeit und Leistungsstörungen</a>).</p>
|
||||||
|
|
||||||
<p>Die NutzerInnen sind für die Daten und Dateien, die sie in we.bstly und damit verbundenen Services ablegen,
|
<p>Die NutzerInnen sind für die Daten und Dateien, die sie in we.bstly und damit verbundenen Services ablegen,
|
||||||
@ -99,7 +99,7 @@ darstellen, betreffen oder beinhalten.
|
|||||||
|
|
||||||
<h3>Datenschutz</h3>
|
<h3>Datenschutz</h3>
|
||||||
<p>Personenbezogene Daten werden ausschließlich zur Bereitstellung von we.bstly und damit verbundenen Services
|
<p>Personenbezogene Daten werden ausschließlich zur Bereitstellung von we.bstly und damit verbundenen Services
|
||||||
verarbeitet. Die Details dazu sind in der <a href="/privacy-policy">Datenschutzerklärung</a> zu finden.
|
verarbeitet. Die Details dazu sind in der <a class="accent" href="/privacy-policy">Datenschutzerklärung</a> zu finden.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
|
||||||
|
@ -905,7 +905,7 @@
|
|||||||
|
|
||||||
<h4 id="Rechenzentrum">Rechenzentrum</h4>
|
<h4 id="Rechenzentrum">Rechenzentrum</h4>
|
||||||
|
|
||||||
<p>Die Daten werden im Rechenzentrum der <a href="https://www.netcup.de/ueber-netcup/rechenzentrum.php"
|
<p>Die Daten werden im Rechenzentrum der <a class="accent" href="https://www.netcup.de/ueber-netcup/rechenzentrum.php"
|
||||||
target="_blank">netcup GmbH</a> gespeichert. Eine regelmäßige, automatisierte Datensicherung der
|
target="_blank">netcup GmbH</a> gespeichert. Eine regelmäßige, automatisierte Datensicherung der
|
||||||
Bestandsdaten
|
Bestandsdaten
|
||||||
wird durchgeführt.
|
wird durchgeführt.
|
||||||
|
@ -3,13 +3,13 @@
|
|||||||
<h2>Nutzungsbedingungen</h2>
|
<h2>Nutzungsbedingungen</h2>
|
||||||
|
|
||||||
<h3>Bereitstellung</h3>
|
<h3>Bereitstellung</h3>
|
||||||
<p>Mit we.bstly stellt der <a href="https://www.bstly.de" target="_blank">Bastelei e. V.</a> eine Plattform
|
<p>Mit we.bstly stellt der <a class="accent" href="https://www.bstly.de" target="_blank">Bastelei e. V.</a> eine Plattform
|
||||||
bereit, um verschiedene digitale Services zeitlich befristet zur Verfügung zu stellen. </p>
|
bereit, um verschiedene digitale Services zeitlich befristet zur Verfügung zu stellen. </p>
|
||||||
<p>we.bstly und damit verbundene Services stehen allen Vereinsmitgliedern des Bastelei e. V. sowie allen
|
<p>we.bstly und damit verbundene Services stehen allen Vereinsmitgliedern des Bastelei e. V. sowie allen
|
||||||
NutzerInnen, die die entsprechenden Services direkt gebucht haben, zur Verfügung.</p>
|
NutzerInnen, die die entsprechenden Services direkt gebucht haben, zur Verfügung.</p>
|
||||||
<p>Gegenstand der Nutzung ist die Bereitstellung we.bstly und den damit verbundenen Services zur Online-Nutzung über das
|
<p>Gegenstand der Nutzung ist die Bereitstellung we.bstly und den damit verbundenen Services zur Online-Nutzung über das
|
||||||
Internet und die Übermittlung und Speicherung von Daten und Dateien der NutzerInnen.</p>
|
Internet und die Übermittlung und Speicherung von Daten und Dateien der NutzerInnen.</p>
|
||||||
<p>we.bstly sowie die verschiedenen Services sind einzeln mit ihren Funktionen unter <a href="/services">Aktive
|
<p>we.bstly sowie die verschiedenen Services sind einzeln mit ihren Funktionen unter <a class="accent" href="/services">Aktive
|
||||||
Services</a> aufgelistet.</p>
|
Services</a> aufgelistet.</p>
|
||||||
|
|
||||||
|
|
||||||
@ -23,7 +23,7 @@
|
|||||||
|
|
||||||
<h3>Datensicherheit</h3>
|
<h3>Datensicherheit</h3>
|
||||||
<p>we.bstly und damit verbundene Services haben Sicherheitsmerkmale implementiert, die Schutz vor Verlust, Missbrauch
|
<p>we.bstly und damit verbundene Services haben Sicherheitsmerkmale implementiert, die Schutz vor Verlust, Missbrauch
|
||||||
und Manipulation der Daten und Dateien bieten. Dennoch kann ein 100% Schutz nicht gewährleistet werden (siehe <a
|
und Manipulation der Daten und Dateien bieten. Dennoch kann ein 100% Schutz nicht gewährleistet werden (siehe <a class="accent"
|
||||||
href="/terms-of-service#availability">Verfügbarkeit und Leistungsstörungen</a>).</p>
|
href="/terms-of-service#availability">Verfügbarkeit und Leistungsstörungen</a>).</p>
|
||||||
|
|
||||||
<p>Die NutzerInnen sind für die Daten und Dateien, die sie in we.bstly und damit verbundenen Services ablegen,
|
<p>Die NutzerInnen sind für die Daten und Dateien, die sie in we.bstly und damit verbundenen Services ablegen,
|
||||||
|
@ -25,12 +25,13 @@ $dark-theme: mat-dark-theme((color: (primary: $dark-primary,
|
|||||||
warn: $light-warn,
|
warn: $light-warn,
|
||||||
)));
|
)));
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Include theme styles for core and each component used in your app.
|
// Include theme styles for core and each component used in your app.
|
||||||
// Alternatively, you can import and @include the theme mixins for each component
|
// Alternatively, you can import and @include the theme mixins for each component
|
||||||
// that you are using.
|
// that you are using.
|
||||||
@include angular-material-theme($light-theme);
|
@include angular-material-theme($light-theme);
|
||||||
|
|
||||||
|
|
||||||
.dark-theme {
|
.dark-theme {
|
||||||
@include angular-material-color($dark-theme);
|
@include angular-material-color($dark-theme);
|
||||||
}
|
}
|
||||||
@ -47,6 +48,11 @@ $dark-theme: mat-dark-theme((color: (primary: $dark-primary,
|
|||||||
src: url(assets/fonts/material_icons.woff2) format('woff2');
|
src: url(assets/fonts/material_icons.woff2) format('woff2');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
a.accent {
|
||||||
|
color: $accent;
|
||||||
|
}
|
||||||
|
|
||||||
.material-icons {
|
.material-icons {
|
||||||
font-family: 'Material Icons';
|
font-family: 'Material Icons';
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
@ -239,6 +245,7 @@ table {
|
|||||||
border: 0;
|
border: 0;
|
||||||
border-spacing: 0;
|
border-spacing: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
background: white;
|
||||||
|
|
||||||
th,
|
th,
|
||||||
td,
|
td,
|
||||||
@ -270,3 +277,27 @@ table {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.dark-theme {
|
||||||
|
table {
|
||||||
|
|
||||||
|
background: #424242;
|
||||||
|
|
||||||
|
th,
|
||||||
|
td,
|
||||||
|
td {
|
||||||
|
color: white;
|
||||||
|
border-bottom-color: rgba(255, 255, 255, 0.12);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
thead {
|
||||||
|
tr {
|
||||||
|
th {
|
||||||
|
color: rgba(255, 255, 255, 0.7);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -4,7 +4,6 @@ $light-primary: mat-palette($mat-gray, 800);
|
|||||||
$light-accent: mat-palette($mat-pink, A200, A100, A400);
|
$light-accent: mat-palette($mat-pink, A200, A100, A400);
|
||||||
$light-warn: mat-palette($mat-red);
|
$light-warn: mat-palette($mat-red);
|
||||||
|
|
||||||
|
|
||||||
$primary: mat-color($light-primary);
|
$primary: mat-color($light-primary);
|
||||||
$accent: mat-color($light-accent);
|
$accent: mat-color($light-accent);
|
||||||
$warn: mat-color($light-warn);
|
$warn: mat-color($light-warn);
|
||||||
|
Loading…
Reference in New Issue
Block a user