mirror of
https://gitlab.com/openstapps/openstapps.git
synced 2025-12-23 14:46:18 +00:00
Compare commits
2 Commits
@openstapp
...
18-overhau
| Author | SHA1 | Date | |
|---|---|---|---|
|
09de4fd033
|
|||
|
|
cb196afded |
5
.changeset/silver-bobcats-cry.md
Normal file
5
.changeset/silver-bobcats-cry.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
'@openstapps/app': minor
|
||||||
|
---
|
||||||
|
|
||||||
|
Queue config update for next launch to not block app launches
|
||||||
5
.changeset/twelve-planes-knock.md
Normal file
5
.changeset/twelve-planes-knock.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
'@openstapps/proxy': minor
|
||||||
|
---
|
||||||
|
|
||||||
|
Send 426 to outdated clients instead of 404
|
||||||
@@ -31,10 +31,11 @@ To Provide your own configuration file you can create a `default.json` file in t
|
|||||||
|
|
||||||
## Status Codes
|
## Status Codes
|
||||||
|
|
||||||
- OutdatedVersions return a `HTTP 404`
|
- Successfull reponses come with a `HTTP 200`
|
||||||
- ActiveVersions return a `HTTP 503` if currently unavailable or the given code by running backend-node
|
|
||||||
- Unsupported versions (not configured as outdated or active) return a `HTTP 404`
|
|
||||||
- No version header given returns a `HTTP 300`
|
- No version header given returns a `HTTP 300`
|
||||||
|
- ActiveVersions return a `HTTP 503` if currently unavailable or the given code by running backend-node
|
||||||
|
- OutdatedVersions return a `HTTP 426`
|
||||||
|
- Unsupported versions (not configured as outdated or active) return a `HTTP 426`
|
||||||
|
|
||||||
**NOTE:** The default configuration expects the client to set a version header: `X-StApps-Version=<version of app>`
|
**NOTE:** The default configuration expects the client to set a version header: `X-StApps-Version=<version of app>`
|
||||||
|
|
||||||
|
|||||||
@@ -14,9 +14,8 @@
|
|||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// ESM is not supported, and cts is not detected, so we use type-checked cjs instead.
|
|
||||||
/** @type {import('../src/common').ConfigFile} */
|
/** @type {import('../src/common').ConfigFile} */
|
||||||
const configFile = {
|
module.exports = {
|
||||||
activeVersions: ['1\\.0\\.\\d+', '2\\.0\\.\\d+'],
|
activeVersions: ['1\\.0\\.\\d+', '2\\.0\\.\\d+'],
|
||||||
hiddenRoutes: ['/bulk'],
|
hiddenRoutes: ['/bulk'],
|
||||||
logFormat: 'default',
|
logFormat: 'default',
|
||||||
@@ -31,5 +30,3 @@ const configFile = {
|
|||||||
dhparam: '/etc/nginx/certs/dhparam.pem',
|
dhparam: '/etc/nginx/certs/dhparam.pem',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export default configFile;
|
|
||||||
|
|||||||
@@ -19,14 +19,15 @@ location {{{ route }}} {
|
|||||||
return 300 'You have to supply a client/app version via the X-StApps-Version header!';
|
return 300 'You have to supply a client/app version via the X-StApps-Version header!';
|
||||||
}
|
}
|
||||||
|
|
||||||
# Version is unsupported or never existed
|
# Version is unsupported by now or never existed (App/Client has to update)
|
||||||
if ($proxyurl = unsupported) {
|
if ($proxyurl = unsupported) {
|
||||||
{{{ cors }}}
|
{{{ cors }}}
|
||||||
return 404;
|
return 426;
|
||||||
}
|
}
|
||||||
# The version existed, but is outdated now (App should update)
|
# The version existed, but is outdated now (App/Client should update)
|
||||||
if ($proxyurl = outdated) {
|
if ($proxyurl = outdated) {
|
||||||
return 404;
|
{{{ cors }}}
|
||||||
|
return 426;
|
||||||
}
|
}
|
||||||
# The version is correct, but backend is not responding
|
# The version is correct, but backend is not responding
|
||||||
if ($proxyurl = unavailable) {
|
if ($proxyurl = unavailable) {
|
||||||
|
|||||||
@@ -34,10 +34,12 @@
|
|||||||
"build": "tsup-node --dts",
|
"build": "tsup-node --dts",
|
||||||
"build:docker": "docker build -t openstapps:proxy ../../.deploy/proxy",
|
"build:docker": "docker build -t openstapps:proxy ../../.deploy/proxy",
|
||||||
"deploy": "pnpm --prod --filter=@openstapps/proxy deploy ../../.deploy/proxy",
|
"deploy": "pnpm --prod --filter=@openstapps/proxy deploy ../../.deploy/proxy",
|
||||||
|
"dev": "tsup --watch --onSuccess \"pnpm run start\"",
|
||||||
"format": "prettier . -c --ignore-path ../../.gitignore",
|
"format": "prettier . -c --ignore-path ../../.gitignore",
|
||||||
"format:fix": "prettier --write . --ignore-path ../../.gitignore",
|
"format:fix": "prettier --write . --ignore-path ../../.gitignore",
|
||||||
"lint": "eslint --ext .ts src/",
|
"lint": "eslint --ext .ts src/",
|
||||||
"lint:fix": "eslint --fix --ext .ts src/",
|
"lint:fix": "eslint --fix --ext .ts src/",
|
||||||
|
"start": "node app.js",
|
||||||
"test": "c8 mocha"
|
"test": "c8 mocha"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ const delay = (ms: number) => new Promise(resolve => setTimeout(resolve, ms));
|
|||||||
* Reads the container information from the docker socket and updates the nginx config if necessary
|
* Reads the container information from the docker socket and updates the nginx config if necessary
|
||||||
*/
|
*/
|
||||||
async function updateNginxConfig() {
|
async function updateNginxConfig() {
|
||||||
const containers = await getContainers();
|
const containers = await getContainers(process.env.DOCKER_SOCKET);
|
||||||
|
|
||||||
const containerHash = containers
|
const containerHash = containers
|
||||||
.map((container: ContainerInfo) => {
|
.map((container: ContainerInfo) => {
|
||||||
@@ -78,4 +78,4 @@ async function updateNginxConfig() {
|
|||||||
|
|
||||||
// start the process that checks the docker socket periodically
|
// start the process that checks the docker socket periodically
|
||||||
// eslint-disable-next-line unicorn/prefer-top-level-await
|
// eslint-disable-next-line unicorn/prefer-top-level-await
|
||||||
updateNginxConfig();
|
await updateNginxConfig();
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
version: '3.7'
|
version: '3.7'
|
||||||
services:
|
services:
|
||||||
database:
|
database:
|
||||||
image: registry.gitlab.com/openstapps/openstapps/database:2.0.0
|
image: registry.gitlab.com/openstapps/openstapps/database:3.0.0-next.4
|
||||||
volumes:
|
volumes:
|
||||||
- ./database:/usr/share/elasticsearch/data
|
- ./database:/usr/share/elasticsearch/data
|
||||||
expose:
|
expose:
|
||||||
@@ -9,7 +9,7 @@ services:
|
|||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
|
||||||
backend:
|
backend:
|
||||||
image: registry.gitlab.com/openstapps/openstapps/backend:3.0.0-next.0
|
image: registry.gitlab.com/openstapps/openstapps/backend:3.0.0-next.4
|
||||||
environment:
|
environment:
|
||||||
ES_ADDR: "http://database:9200"
|
ES_ADDR: "http://database:9200"
|
||||||
NODE_CONFIG_ENV: "elasticsearch"
|
NODE_CONFIG_ENV: "elasticsearch"
|
||||||
@@ -27,17 +27,17 @@ services:
|
|||||||
- database
|
- database
|
||||||
|
|
||||||
api:
|
api:
|
||||||
image: registry.gitlab.com/openstapps/openstapps/api:3.0.0-next.0
|
image: registry.gitlab.com/openstapps/openstapps/api:3.0.0-next.4
|
||||||
links:
|
links:
|
||||||
- "backend"
|
- "backend"
|
||||||
|
|
||||||
minimal-connector:
|
minimal-connector:
|
||||||
image: registry.gitlab.com/openstapps/minimal-connector:core-0.23
|
image: registry.gitlab.com/openstapps/openstapps/minimal-connector:3.0.0-next.4
|
||||||
container_name: minimal-connector-0.23
|
container_name: minimal-connector-0.23
|
||||||
command: ["http://backend:3000", "minimal-connector", "f-u"]
|
command: ["http://backend:3000", "minimal-connector", "f-u"]
|
||||||
|
|
||||||
app:
|
app:
|
||||||
image: registry.gitlab.com/openstapps/app/executable:core-0.23
|
image: registry.gitlab.com/openstapps/openstapps/app:3.0.0-next.4
|
||||||
expose:
|
expose:
|
||||||
- 8100
|
- 8100
|
||||||
ports:
|
ports:
|
||||||
|
|||||||
@@ -1,19 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2020 StApps
|
|
||||||
* This program is free software: you can redistribute it and/or modify it
|
|
||||||
* under the terms of the GNU General Public License as published by the Free
|
|
||||||
* Software Foundation, version 3.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
|
||||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
|
||||||
* more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License along with
|
|
||||||
* this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
import {NGXLogger} from 'ngx-logger';
|
|
||||||
|
|
||||||
export let logger: NGXLogger;
|
|
||||||
|
|
||||||
export const initLogger = (newLogger: NGXLogger) => (logger = newLogger);
|
|
||||||
@@ -21,17 +21,13 @@ import {RouteReuseStrategy} from '@angular/router';
|
|||||||
import {IonicModule, IonicRouteStrategy, Platform} from '@ionic/angular';
|
import {IonicModule, IonicRouteStrategy, Platform} from '@ionic/angular';
|
||||||
import {TranslateLoader, TranslateModule, TranslateService} from '@ngx-translate/core';
|
import {TranslateLoader, TranslateModule, TranslateService} from '@ngx-translate/core';
|
||||||
import {TranslateHttpLoader} from '@ngx-translate/http-loader';
|
import {TranslateHttpLoader} from '@ngx-translate/http-loader';
|
||||||
import moment from 'moment';
|
|
||||||
import 'moment/min/locales';
|
import 'moment/min/locales';
|
||||||
import {LoggerModule, NGXLogger, NgxLoggerLevel} from 'ngx-logger';
|
import {LoggerModule, NgxLoggerLevel} from 'ngx-logger';
|
||||||
import SwiperCore, {FreeMode, Navigation} from 'swiper';
|
import SwiperCore, {FreeMode, Navigation} from 'swiper';
|
||||||
|
|
||||||
import {environment} from '../environments/environment';
|
import {environment} from '../environments/environment';
|
||||||
import {AppRoutingModule} from './app-routing.module';
|
import {AppRoutingModule} from './app-routing.module';
|
||||||
import {AppComponent} from './app.component';
|
import {AppComponent} from './app.component';
|
||||||
import {CatalogModule} from './modules/catalog/catalog.module';
|
import {CatalogModule} from './modules/catalog/catalog.module';
|
||||||
import {ConfigModule} from './modules/config/config.module';
|
|
||||||
import {ConfigProvider} from './modules/config/config.provider';
|
|
||||||
import {DashboardModule} from './modules/dashboard/dashboard.module';
|
import {DashboardModule} from './modules/dashboard/dashboard.module';
|
||||||
import {DataModule} from './modules/data/data.module';
|
import {DataModule} from './modules/data/data.module';
|
||||||
import {HebisModule} from './modules/hebis/hebis.module';
|
import {HebisModule} from './modules/hebis/hebis.module';
|
||||||
@@ -40,11 +36,9 @@ import {MenuModule} from './modules/menu/menu.module';
|
|||||||
import {NewsModule} from './modules/news/news.module';
|
import {NewsModule} from './modules/news/news.module';
|
||||||
import {ScheduleModule} from './modules/schedule/schedule.module';
|
import {ScheduleModule} from './modules/schedule/schedule.module';
|
||||||
import {SettingsModule} from './modules/settings/settings.module';
|
import {SettingsModule} from './modules/settings/settings.module';
|
||||||
import {SettingsProvider} from './modules/settings/settings.provider';
|
|
||||||
import {StorageModule} from './modules/storage/storage.module';
|
import {StorageModule} from './modules/storage/storage.module';
|
||||||
import {ThingTranslateModule} from './translation/thing-translate.module';
|
import {ThingTranslateModule} from './translation/thing-translate.module';
|
||||||
import {UtilModule} from './util/util.module';
|
import {UtilModule} from './util/util.module';
|
||||||
import {initLogger} from './_helpers/ts-logger';
|
|
||||||
import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
|
import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
|
||||||
import {AboutModule} from './modules/about/about.module';
|
import {AboutModule} from './modules/about/about.module';
|
||||||
import {FavoritesModule} from './modules/favorites/favorites.module';
|
import {FavoritesModule} from './modules/favorites/favorites.module';
|
||||||
@@ -54,80 +48,22 @@ import {DebugDataCollectorService} from './modules/data/debug-data-collector.ser
|
|||||||
import {AuthModule} from './modules/auth/auth.module';
|
import {AuthModule} from './modules/auth/auth.module';
|
||||||
import {BackgroundModule} from './modules/background/background.module';
|
import {BackgroundModule} from './modules/background/background.module';
|
||||||
import {LibraryModule} from './modules/library/library.module';
|
import {LibraryModule} from './modules/library/library.module';
|
||||||
import {StorageProvider} from './modules/storage/storage.provider';
|
|
||||||
import {AssessmentsModule} from './modules/assessments/assessments.module';
|
import {AssessmentsModule} from './modules/assessments/assessments.module';
|
||||||
import {ServiceHandlerInterceptor} from './_helpers/service-handler.interceptor';
|
import {ServiceHandlerInterceptor} from './_helpers/service-handler.interceptor';
|
||||||
import {RoutingStackService} from './util/routing-stack.service';
|
|
||||||
import {SCLanguageCode, SCSettingValue} from '@openstapps/core';
|
|
||||||
import {DefaultAuthService} from './modules/auth/default-auth.service';
|
|
||||||
import {PAIAAuthService} from './modules/auth/paia/paia-auth.service';
|
|
||||||
import {IonIconModule} from './util/ion-icon/ion-icon.module';
|
import {IonIconModule} from './util/ion-icon/ion-icon.module';
|
||||||
import {NavigationModule} from './modules/menu/navigation/navigation.module';
|
import {NavigationModule} from './modules/menu/navigation/navigation.module';
|
||||||
import {browserFactory, SimpleBrowser} from './util/browser.factory';
|
import {browserFactory, SimpleBrowser} from './util/browser.factory';
|
||||||
import {getDateFnsLocale} from './translation/dfns-locale';
|
import {ConfigProvider} from './modules/config/config.provider';
|
||||||
import {setDefaultOptions} from 'date-fns';
|
import {SettingsProvider} from './modules/settings/settings.provider';
|
||||||
import {DateFnsConfigurationService} from 'ngx-date-fns';
|
import {TranslateServiceWrapper} from './translation/translate-service-wrapper';
|
||||||
|
import {DefaultAuthService} from './modules/auth/default-auth.service';
|
||||||
|
import {PAIAAuthService} from './modules/auth/paia/paia-auth.service';
|
||||||
|
import {StorageProvider} from './modules/storage/storage.provider';
|
||||||
|
|
||||||
registerLocaleData(localeDe);
|
registerLocaleData(localeDe);
|
||||||
|
|
||||||
SwiperCore.use([FreeMode, Navigation]);
|
SwiperCore.use([FreeMode, Navigation]);
|
||||||
|
|
||||||
/**
|
|
||||||
* Initializes data needed on startup
|
|
||||||
*/
|
|
||||||
export function initializerFactory(
|
|
||||||
storageProvider: StorageProvider,
|
|
||||||
logger: NGXLogger,
|
|
||||||
settingsProvider: SettingsProvider,
|
|
||||||
configProvider: ConfigProvider,
|
|
||||||
translateService: TranslateService,
|
|
||||||
_routingStackService: RoutingStackService,
|
|
||||||
defaultAuthService: DefaultAuthService,
|
|
||||||
paiaAuthService: PAIAAuthService,
|
|
||||||
dateFnsConfigurationService: DateFnsConfigurationService,
|
|
||||||
) {
|
|
||||||
return async () => {
|
|
||||||
initLogger(logger);
|
|
||||||
await storageProvider.init();
|
|
||||||
await configProvider.init();
|
|
||||||
await settingsProvider.init();
|
|
||||||
try {
|
|
||||||
if (configProvider.firstSession) {
|
|
||||||
// set language from browser
|
|
||||||
await settingsProvider.setSettingValue(
|
|
||||||
'profile',
|
|
||||||
'language',
|
|
||||||
translateService.getBrowserLang() as SCSettingValue,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
const languageCode = (await settingsProvider.getValue('profile', 'language')) as string;
|
|
||||||
// this language will be used as a fallback when a translation isn't found in the current language
|
|
||||||
translateService.setDefaultLang('en');
|
|
||||||
translateService.use(languageCode);
|
|
||||||
moment.locale(languageCode);
|
|
||||||
const dateFnsLocale = await getDateFnsLocale(languageCode as SCLanguageCode);
|
|
||||||
setDefaultOptions({locale: dateFnsLocale});
|
|
||||||
dateFnsConfigurationService.setLocale(dateFnsLocale);
|
|
||||||
|
|
||||||
await defaultAuthService.init();
|
|
||||||
await paiaAuthService.init();
|
|
||||||
} catch (error) {
|
|
||||||
logger.warn(error);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* TODO
|
|
||||||
* @param http TODO
|
|
||||||
*/
|
|
||||||
export function createTranslateLoader(http: HttpClient) {
|
|
||||||
return new TranslateHttpLoader(http, './assets/i18n/', '.json');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* TODO
|
|
||||||
*/
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
bootstrap: [AppComponent],
|
bootstrap: [AppComponent],
|
||||||
declarations: [AppComponent],
|
declarations: [AppComponent],
|
||||||
@@ -141,7 +77,6 @@ export function createTranslateLoader(http: HttpClient) {
|
|||||||
BrowserAnimationsModule,
|
BrowserAnimationsModule,
|
||||||
CatalogModule,
|
CatalogModule,
|
||||||
CommonModule,
|
CommonModule,
|
||||||
ConfigModule,
|
|
||||||
DashboardModule,
|
DashboardModule,
|
||||||
DataModule,
|
DataModule,
|
||||||
HebisModule,
|
HebisModule,
|
||||||
@@ -165,7 +100,9 @@ export function createTranslateLoader(http: HttpClient) {
|
|||||||
loader: {
|
loader: {
|
||||||
deps: [HttpClient],
|
deps: [HttpClient],
|
||||||
provide: TranslateLoader,
|
provide: TranslateLoader,
|
||||||
useFactory: createTranslateLoader,
|
useFactory(http: HttpClient) {
|
||||||
|
return new TranslateHttpLoader(http, './assets/i18n/', '.json');
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
UtilModule,
|
UtilModule,
|
||||||
@@ -175,6 +112,30 @@ export function createTranslateLoader(http: HttpClient) {
|
|||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
providers: [
|
providers: [
|
||||||
|
{
|
||||||
|
provide: APP_INITIALIZER,
|
||||||
|
useFactory:
|
||||||
|
(...providers: Array<{beforeAppInit(): Promise<void>}>) =>
|
||||||
|
async () => {
|
||||||
|
for (const provider of providers) {
|
||||||
|
await provider.beforeAppInit();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// Declare initialization (order matters)
|
||||||
|
deps: [
|
||||||
|
StorageProvider,
|
||||||
|
ConfigProvider,
|
||||||
|
SettingsProvider,
|
||||||
|
TranslateService,
|
||||||
|
DefaultAuthService,
|
||||||
|
PAIAAuthService,
|
||||||
|
],
|
||||||
|
multi: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: TranslateService,
|
||||||
|
useClass: TranslateServiceWrapper,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
provide: RouteReuseStrategy,
|
provide: RouteReuseStrategy,
|
||||||
useClass: IonicRouteStrategy,
|
useClass: IonicRouteStrategy,
|
||||||
@@ -188,22 +149,6 @@ export function createTranslateLoader(http: HttpClient) {
|
|||||||
useFactory: browserFactory,
|
useFactory: browserFactory,
|
||||||
deps: [Platform],
|
deps: [Platform],
|
||||||
},
|
},
|
||||||
{
|
|
||||||
provide: APP_INITIALIZER,
|
|
||||||
multi: true,
|
|
||||||
deps: [
|
|
||||||
StorageProvider,
|
|
||||||
NGXLogger,
|
|
||||||
SettingsProvider,
|
|
||||||
ConfigProvider,
|
|
||||||
TranslateService,
|
|
||||||
RoutingStackService,
|
|
||||||
DefaultAuthService,
|
|
||||||
PAIAAuthService,
|
|
||||||
DateFnsConfigurationService,
|
|
||||||
],
|
|
||||||
useFactory: initializerFactory,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
provide: HTTP_INTERCEPTORS,
|
provide: HTTP_INTERCEPTORS,
|
||||||
useClass: ServiceHandlerInterceptor,
|
useClass: ServiceHandlerInterceptor,
|
||||||
|
|||||||
10
frontend/app/src/app/before-app-init.ts
Normal file
10
frontend/app/src/app/before-app-init.ts
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
/**
|
||||||
|
* Services or providers implementing this interface
|
||||||
|
* must be added to the `APP_INITIALIZER` deps
|
||||||
|
*/
|
||||||
|
export interface BeforeAppInit {
|
||||||
|
/**
|
||||||
|
* Any logic that has to run before the app is initialized
|
||||||
|
*/
|
||||||
|
beforeAppInit(): Promise<void>;
|
||||||
|
}
|
||||||
@@ -14,10 +14,9 @@
|
|||||||
*/
|
*/
|
||||||
import {Component, OnInit} from '@angular/core';
|
import {Component, OnInit} from '@angular/core';
|
||||||
import {ActivatedRoute} from '@angular/router';
|
import {ActivatedRoute} from '@angular/router';
|
||||||
import {SCAboutPage, SCAppConfiguration} from '@openstapps/core';
|
import {SCAboutPage} from '@openstapps/core';
|
||||||
import {ConfigProvider} from '../../config/config.provider';
|
|
||||||
import packageJson from '../../../../../package.json';
|
import packageJson from '../../../../../package.json';
|
||||||
import config from 'capacitor.config';
|
import {ConfigProvider} from '../../config/config.provider';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'about-page',
|
selector: 'about-page',
|
||||||
@@ -27,15 +26,12 @@ import config from 'capacitor.config';
|
|||||||
export class AboutPageComponent implements OnInit {
|
export class AboutPageComponent implements OnInit {
|
||||||
content: SCAboutPage;
|
content: SCAboutPage;
|
||||||
|
|
||||||
appName = config.appName;
|
|
||||||
|
|
||||||
version = packageJson.version;
|
version = packageJson.version;
|
||||||
|
|
||||||
constructor(private readonly route: ActivatedRoute, private readonly configProvider: ConfigProvider) {}
|
constructor(readonly route: ActivatedRoute, readonly config: ConfigProvider) {}
|
||||||
|
|
||||||
async ngOnInit() {
|
ngOnInit() {
|
||||||
const route = this.route.snapshot.url.map(it => it.path).join('/');
|
const route = this.route.snapshot.url.map(it => it.path).join('/');
|
||||||
this.content =
|
this.content = this.config.app.aboutPages[route] ?? {};
|
||||||
(this.configProvider.getValue('aboutPages') as SCAppConfiguration['aboutPages'])[route] ?? {};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,7 +25,7 @@
|
|||||||
</ion-toolbar>
|
</ion-toolbar>
|
||||||
</ion-header>
|
</ion-header>
|
||||||
<ion-content parallax *ngIf="content">
|
<ion-content parallax *ngIf="content">
|
||||||
<ion-text>{{ appName }} v{{ version }}</ion-text>
|
<ion-text>{{ config.app.name }} v{{ version }}</ion-text>
|
||||||
<div class="page-content">
|
<div class="page-content">
|
||||||
<about-page-content *ngFor="let element of content.content" [content]="element"></about-page-content>
|
<about-page-content *ngFor="let element of content.content" [content]="element"></about-page-content>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ import {FormsModule} from '@angular/forms';
|
|||||||
import {IonicModule} from '@ionic/angular';
|
import {IonicModule} from '@ionic/angular';
|
||||||
import {TranslateModule} from '@ngx-translate/core';
|
import {TranslateModule} from '@ngx-translate/core';
|
||||||
import {ThingTranslateModule} from '../../translation/thing-translate.module';
|
import {ThingTranslateModule} from '../../translation/thing-translate.module';
|
||||||
import {ConfigProvider} from '../config/config.provider';
|
|
||||||
import {AboutPageComponent} from './about-page/about-page.component';
|
import {AboutPageComponent} from './about-page/about-page.component';
|
||||||
import {MarkdownModule} from 'ngx-markdown';
|
import {MarkdownModule} from 'ngx-markdown';
|
||||||
import {AboutPageContentComponent} from './about-page/about-page-content.component';
|
import {AboutPageContentComponent} from './about-page/about-page-content.component';
|
||||||
@@ -64,6 +63,5 @@ const settingsRoutes: Routes = [
|
|||||||
ScrollingModule,
|
ScrollingModule,
|
||||||
UtilModule,
|
UtilModule,
|
||||||
],
|
],
|
||||||
providers: [ConfigProvider],
|
|
||||||
})
|
})
|
||||||
export class AboutModule {}
|
export class AboutModule {}
|
||||||
|
|||||||
@@ -13,11 +13,12 @@
|
|||||||
* this program. If not, see <https://www.gnu.org/licenses/>.
|
* this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
import {Injectable} from '@angular/core';
|
import {Injectable} from '@angular/core';
|
||||||
import {ConfigProvider} from '../config/config.provider';
|
|
||||||
import {SCAssessment, SCUuid} from '@openstapps/core';
|
import {SCAssessment, SCUuid} from '@openstapps/core';
|
||||||
import {DefaultAuthService} from '../auth/default-auth.service';
|
import {DefaultAuthService} from '../auth/default-auth.service';
|
||||||
import {HttpClient} from '@angular/common/http';
|
import {HttpClient} from '@angular/common/http';
|
||||||
import {uniqBy, keyBy} from '@openstapps/collection-utils';
|
import {uniqBy, keyBy} from '@openstapps/collection-utils';
|
||||||
|
import {firstValueFrom} from 'rxjs';
|
||||||
|
import {ConfigProvider} from '../config/config.provider';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
@@ -65,7 +66,7 @@ export class AssessmentsProvider {
|
|||||||
cacheMaxAge = 15 * 60 * 1000;
|
cacheMaxAge = 15 * 60 * 1000;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
readonly configProvider: ConfigProvider,
|
readonly config: ConfigProvider,
|
||||||
readonly defaultAuth: DefaultAuthService,
|
readonly defaultAuth: DefaultAuthService,
|
||||||
readonly http: HttpClient,
|
readonly http: HttpClient,
|
||||||
) {}
|
) {}
|
||||||
@@ -91,17 +92,16 @@ export class AssessmentsProvider {
|
|||||||
return this.cache;
|
return this.cache;
|
||||||
}
|
}
|
||||||
|
|
||||||
const url = this.configProvider.config.app.features.extern?.hisometry.url;
|
const url = this.config.app.features.extern?.hisometry.url;
|
||||||
if (!url) throw new Error('Config lacks url for hisometry');
|
if (!url) throw new Error('Config lacks url for hisometry');
|
||||||
|
|
||||||
this.cache = this.http
|
this.cache = firstValueFrom(
|
||||||
.get<{data: SCAssessment[]}>(`${url}/${this.assessmentPath}`, {
|
this.http.get<{data: SCAssessment[]}>(`${url}/${this.assessmentPath}`, {
|
||||||
headers: {
|
headers: {
|
||||||
Authorization: `Bearer ${accessToken ?? (await this.defaultAuth.getValidToken()).accessToken}`,
|
Authorization: `Bearer ${accessToken ?? (await this.defaultAuth.getValidToken()).accessToken}`,
|
||||||
},
|
},
|
||||||
})
|
}),
|
||||||
.toPromise()
|
).then(it => {
|
||||||
.then(it => {
|
|
||||||
this.cacheTimestamp = Date.now();
|
this.cacheTimestamp = Date.now();
|
||||||
|
|
||||||
return it?.data ?? [];
|
return it?.data ?? [];
|
||||||
|
|||||||
@@ -12,24 +12,18 @@
|
|||||||
* You should have received a copy of the GNU General Public License along with
|
* You should have received a copy of the GNU General Public License along with
|
||||||
* this program. If not, see <https://www.gnu.org/licenses/>.
|
* this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {Injectable} from '@angular/core';
|
import {Injectable} from '@angular/core';
|
||||||
import {IPAIAAuthAction} from './paia/paia-auth-action';
|
import {IPAIAAuthAction} from './paia/paia-auth-action';
|
||||||
import {AuthActions, IAuthAction} from 'ionic-appauth';
|
import {AuthActions, IAuthAction} from 'ionic-appauth';
|
||||||
import {TranslateService} from '@ngx-translate/core';
|
import {TranslateService} from '@ngx-translate/core';
|
||||||
import {JSONPath} from 'jsonpath-plus';
|
import {JSONPath} from 'jsonpath-plus';
|
||||||
import {
|
import {SCAuthorizationProviderType, SCUserConfiguration, SCUserConfigurationMap} from '@openstapps/core';
|
||||||
SCAuthorizationProvider,
|
|
||||||
SCAuthorizationProviderType,
|
|
||||||
SCUserConfiguration,
|
|
||||||
SCUserConfigurationMap,
|
|
||||||
} from '@openstapps/core';
|
|
||||||
import {ConfigProvider} from '../config/config.provider';
|
|
||||||
import {StorageProvider} from '../storage/storage.provider';
|
import {StorageProvider} from '../storage/storage.provider';
|
||||||
import {DefaultAuthService} from './default-auth.service';
|
import {DefaultAuthService} from './default-auth.service';
|
||||||
import {PAIAAuthService} from './paia/paia-auth.service';
|
import {PAIAAuthService} from './paia/paia-auth.service';
|
||||||
import {SimpleBrowser} from '../../util/browser.factory';
|
import {SimpleBrowser} from '../../util/browser.factory';
|
||||||
import {AlertController} from '@ionic/angular';
|
import {AlertController} from '@ionic/angular';
|
||||||
|
import {ConfigProvider} from '../config/config.provider';
|
||||||
|
|
||||||
const AUTH_ORIGIN_PATH = 'stapps.auth.origin_path';
|
const AUTH_ORIGIN_PATH = 'stapps.auth.origin_path';
|
||||||
|
|
||||||
@@ -37,23 +31,19 @@ const AUTH_ORIGIN_PATH = 'stapps.auth.origin_path';
|
|||||||
providedIn: 'root',
|
providedIn: 'root',
|
||||||
})
|
})
|
||||||
export class AuthHelperService {
|
export class AuthHelperService {
|
||||||
userConfigurationMap: SCUserConfigurationMap;
|
get userConfigurationMap(): SCUserConfigurationMap {
|
||||||
|
return this.config.auth.default!.endpoints.mapping;
|
||||||
|
}
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private translateService: TranslateService,
|
private translateService: TranslateService,
|
||||||
private configProvider: ConfigProvider,
|
|
||||||
private storageProvider: StorageProvider,
|
private storageProvider: StorageProvider,
|
||||||
private defaultAuth: DefaultAuthService,
|
private defaultAuth: DefaultAuthService,
|
||||||
private paiaAuth: PAIAAuthService,
|
private paiaAuth: PAIAAuthService,
|
||||||
private browser: SimpleBrowser,
|
private browser: SimpleBrowser,
|
||||||
private alertController: AlertController,
|
private alertController: AlertController,
|
||||||
) {
|
private config: ConfigProvider,
|
||||||
this.userConfigurationMap = (
|
) {}
|
||||||
this.configProvider.getAnyValue('auth') as {
|
|
||||||
default: SCAuthorizationProvider;
|
|
||||||
}
|
|
||||||
).default.endpoints.mapping;
|
|
||||||
}
|
|
||||||
|
|
||||||
public getAuthMessage(provider: SCAuthorizationProviderType, action: IAuthAction | IPAIAAuthAction) {
|
public getAuthMessage(provider: SCAuthorizationProviderType, action: IAuthAction | IPAIAAuthAction) {
|
||||||
let message: string | undefined;
|
let message: string | undefined;
|
||||||
|
|||||||
@@ -175,7 +175,7 @@ export abstract class AuthService implements IAuthService {
|
|||||||
|
|
||||||
public async init() {
|
public async init() {
|
||||||
this.setupAuthorizationNotifier();
|
this.setupAuthorizationNotifier();
|
||||||
this.loadTokenFromStorage();
|
await this.loadTokenFromStorage();
|
||||||
this.addActionObserver(this._actionHistory);
|
this.addActionObserver(this._actionHistory);
|
||||||
this.addActionObserver(this._session);
|
this.addActionObserver(this._session);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,29 +12,26 @@
|
|||||||
* You should have received a copy of the GNU General Public License along with
|
* You should have received a copy of the GNU General Public License along with
|
||||||
* this program. If not, see <https://www.gnu.org/licenses/>.
|
* this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {
|
import {
|
||||||
AuthorizationRequestHandler,
|
AuthorizationRequestHandler,
|
||||||
AuthorizationServiceConfiguration,
|
AuthorizationServiceConfiguration,
|
||||||
JQueryRequestor,
|
JQueryRequestor,
|
||||||
LocalStorageBackend,
|
LocalStorageBackend,
|
||||||
Requestor,
|
|
||||||
StorageBackend,
|
|
||||||
TokenRequestHandler,
|
TokenRequestHandler,
|
||||||
} from '@openid/appauth';
|
} from '@openid/appauth';
|
||||||
import {AuthActionBuilder, Browser, DefaultBrowser, EndSessionHandler, UserInfoHandler} from 'ionic-appauth';
|
import {AuthActionBuilder, DefaultBrowser, EndSessionHandler, UserInfoHandler} from 'ionic-appauth';
|
||||||
import {ConfigProvider} from '../config/config.provider';
|
|
||||||
import {SCAuthorizationProvider} from '@openstapps/core';
|
|
||||||
import {getClientConfig, getEndpointsConfig} from './auth.provider.methods';
|
import {getClientConfig, getEndpointsConfig} from './auth.provider.methods';
|
||||||
import {Injectable} from '@angular/core';
|
import {Injectable} from '@angular/core';
|
||||||
import {AuthService} from './auth.service';
|
import {AuthService} from './auth.service';
|
||||||
|
import {ConfigProvider} from '../config/config.provider';
|
||||||
|
import {BeforeAppInit} from '../../before-app-init';
|
||||||
|
|
||||||
const TOKEN_RESPONSE_KEY = 'token_response';
|
const TOKEN_RESPONSE_KEY = 'token_response';
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root',
|
providedIn: 'root',
|
||||||
})
|
})
|
||||||
export class DefaultAuthService extends AuthService {
|
export class DefaultAuthService extends AuthService implements BeforeAppInit {
|
||||||
public localConfiguration: AuthorizationServiceConfiguration;
|
public localConfiguration: AuthorizationServiceConfiguration;
|
||||||
|
|
||||||
protected tokenHandler: TokenRequestHandler;
|
protected tokenHandler: TokenRequestHandler;
|
||||||
@@ -45,13 +42,17 @@ export class DefaultAuthService extends AuthService {
|
|||||||
|
|
||||||
protected endSessionHandler: EndSessionHandler;
|
protected endSessionHandler: EndSessionHandler;
|
||||||
|
|
||||||
constructor(
|
constructor(private config: ConfigProvider) {
|
||||||
protected browser: Browser = new DefaultBrowser(),
|
super(new DefaultBrowser(), new LocalStorageBackend(), new JQueryRequestor());
|
||||||
protected storage: StorageBackend = new LocalStorageBackend(),
|
}
|
||||||
protected requestor: Requestor = new JQueryRequestor(),
|
|
||||||
private readonly configProvider: ConfigProvider,
|
async beforeAppInit() {
|
||||||
) {
|
this.authConfig = getClientConfig('default', this.config.auth);
|
||||||
super(browser, storage, requestor);
|
this.localConfiguration = new AuthorizationServiceConfiguration(
|
||||||
|
getEndpointsConfig('default', this.config.auth),
|
||||||
|
);
|
||||||
|
|
||||||
|
await super.init();
|
||||||
}
|
}
|
||||||
|
|
||||||
get configuration(): Promise<AuthorizationServiceConfiguration> {
|
get configuration(): Promise<AuthorizationServiceConfiguration> {
|
||||||
@@ -60,22 +61,6 @@ export class DefaultAuthService extends AuthService {
|
|||||||
return Promise.resolve(this.localConfiguration);
|
return Promise.resolve(this.localConfiguration);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async init() {
|
|
||||||
this.setupConfiguration();
|
|
||||||
this.setupAuthorizationNotifier();
|
|
||||||
await this.loadTokenFromStorage();
|
|
||||||
}
|
|
||||||
|
|
||||||
setupConfiguration() {
|
|
||||||
const authConfig = this.configProvider.getAnyValue('auth') as {
|
|
||||||
default: SCAuthorizationProvider;
|
|
||||||
};
|
|
||||||
this.authConfig = getClientConfig('default', authConfig);
|
|
||||||
this.localConfiguration = new AuthorizationServiceConfiguration(
|
|
||||||
getEndpointsConfig('default', authConfig),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async signOut() {
|
public async signOut() {
|
||||||
await this.revokeTokens().catch(error => {
|
await this.revokeTokens().catch(error => {
|
||||||
this.notifyActionListers(AuthActionBuilder.SignOutFailed(error));
|
this.notifyActionListers(AuthActionBuilder.SignOutFailed(error));
|
||||||
|
|||||||
@@ -12,7 +12,6 @@
|
|||||||
* You should have received a copy of the GNU General Public License along with
|
* You should have received a copy of the GNU General Public License along with
|
||||||
* this program. If not, see <https://www.gnu.org/licenses/>.
|
* this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {
|
import {
|
||||||
AuthorizationError,
|
AuthorizationError,
|
||||||
AuthorizationRequest,
|
AuthorizationRequest,
|
||||||
@@ -47,10 +46,10 @@ import {PAIAAuthorizationResponse} from './paia-authorization-response';
|
|||||||
import {PAIAAuthorizationNotifier} from './paia-authorization-notifier';
|
import {PAIAAuthorizationNotifier} from './paia-authorization-notifier';
|
||||||
import {PAIATokenResponse} from './paia-token-response';
|
import {PAIATokenResponse} from './paia-token-response';
|
||||||
import {IPAIAAuthAction, PAIAAuthActionBuilder} from './paia-auth-action';
|
import {IPAIAAuthAction, PAIAAuthActionBuilder} from './paia-auth-action';
|
||||||
import {SCAuthorizationProvider} from '@openstapps/core';
|
|
||||||
import {ConfigProvider} from '../../config/config.provider';
|
|
||||||
import {getClientConfig, getEndpointsConfig} from '../auth.provider.methods';
|
import {getClientConfig, getEndpointsConfig} from '../auth.provider.methods';
|
||||||
import {Injectable} from '@angular/core';
|
import {Injectable} from '@angular/core';
|
||||||
|
import {ConfigProvider} from '../../config/config.provider';
|
||||||
|
import {BeforeAppInit} from '../../../before-app-init';
|
||||||
|
|
||||||
const TOKEN_RESPONSE_KEY = 'paia_token_response';
|
const TOKEN_RESPONSE_KEY = 'paia_token_response';
|
||||||
const AUTH_EXPIRY_BUFFER = 10 * 60 * -1; // 10 minutes in seconds
|
const AUTH_EXPIRY_BUFFER = 10 * 60 * -1; // 10 minutes in seconds
|
||||||
@@ -64,10 +63,8 @@ export interface IAuthService {
|
|||||||
getValidToken(buffer?: number): Promise<PAIATokenResponse>;
|
getValidToken(buffer?: number): Promise<PAIATokenResponse>;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({providedIn: 'root'})
|
||||||
providedIn: 'root',
|
export class PAIAAuthService implements BeforeAppInit {
|
||||||
})
|
|
||||||
export class PAIAAuthService {
|
|
||||||
private _authConfig?: IAuthConfig;
|
private _authConfig?: IAuthConfig;
|
||||||
|
|
||||||
private _authSubject: AuthSubject = new AuthSubject();
|
private _authSubject: AuthSubject = new AuthSubject();
|
||||||
@@ -97,7 +94,7 @@ export class PAIAAuthService {
|
|||||||
protected browser: Browser = new DefaultBrowser(),
|
protected browser: Browser = new DefaultBrowser(),
|
||||||
protected storage: StorageBackend = new LocalStorageBackend(),
|
protected storage: StorageBackend = new LocalStorageBackend(),
|
||||||
protected requestor: Requestor = new JQueryRequestor(),
|
protected requestor: Requestor = new JQueryRequestor(),
|
||||||
private readonly configProvider: ConfigProvider,
|
private config: ConfigProvider,
|
||||||
) {
|
) {
|
||||||
this.tokenHandler = new PAIATokenRequestHandler(requestor);
|
this.tokenHandler = new PAIATokenRequestHandler(requestor);
|
||||||
this.userInfoHandler = new IonicUserInfoHandler(requestor);
|
this.userInfoHandler = new IonicUserInfoHandler(requestor);
|
||||||
@@ -110,6 +107,16 @@ export class PAIAAuthService {
|
|||||||
this.endSessionHandler = new IonicEndSessionHandler(browser);
|
this.endSessionHandler = new IonicEndSessionHandler(browser);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async beforeAppInit() {
|
||||||
|
this.authConfig = getClientConfig('paia', this.config.auth);
|
||||||
|
this.localConfiguration = new AuthorizationServiceConfiguration(
|
||||||
|
getEndpointsConfig('paia', this.config.auth),
|
||||||
|
);
|
||||||
|
|
||||||
|
this.setupAuthorizationNotifier();
|
||||||
|
await this.loadTokenFromStorage();
|
||||||
|
}
|
||||||
|
|
||||||
get token$(): Observable<PAIATokenResponse | undefined> {
|
get token$(): Observable<PAIATokenResponse | undefined> {
|
||||||
return this._tokenSubject.asObservable();
|
return this._tokenSubject.asObservable();
|
||||||
}
|
}
|
||||||
@@ -147,20 +154,6 @@ export class PAIAAuthService {
|
|||||||
return Promise.resolve(this.localConfiguration);
|
return Promise.resolve(this.localConfiguration);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async init() {
|
|
||||||
this.setupConfiguration();
|
|
||||||
this.setupAuthorizationNotifier();
|
|
||||||
await this.loadTokenFromStorage();
|
|
||||||
}
|
|
||||||
|
|
||||||
setupConfiguration() {
|
|
||||||
const authConfig = this.configProvider.getAnyValue('auth') as {
|
|
||||||
paia: SCAuthorizationProvider;
|
|
||||||
};
|
|
||||||
this.authConfig = getClientConfig('paia', authConfig);
|
|
||||||
this.localConfiguration = new AuthorizationServiceConfiguration(getEndpointsConfig('paia', authConfig));
|
|
||||||
}
|
|
||||||
|
|
||||||
protected notifyActionListers(action: IPAIAAuthAction) {
|
protected notifyActionListers(action: IPAIAAuthAction) {
|
||||||
/* eslint-disable unicorn/no-useless-undefined */
|
/* eslint-disable unicorn/no-useless-undefined */
|
||||||
switch (action.action) {
|
switch (action.action) {
|
||||||
|
|||||||
@@ -12,7 +12,6 @@
|
|||||||
* You should have received a copy of the GNU General Public License along with
|
* You should have received a copy of the GNU General Public License along with
|
||||||
* this program. If not, see <https://www.gnu.org/licenses/>.
|
* this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {Calendar} from '@awesome-cordova-plugins/calendar/ngx';
|
import {Calendar} from '@awesome-cordova-plugins/calendar/ngx';
|
||||||
import {Injectable} from '@angular/core';
|
import {Injectable} from '@angular/core';
|
||||||
import {ICalEvent} from './ical/ical';
|
import {ICalEvent} from './ical/ical';
|
||||||
@@ -29,18 +28,18 @@ const RECURRENCE_PATTERNS: Partial<Record<unitOfTime.Diff, string | undefined>>
|
|||||||
day: 'daily',
|
day: 'daily',
|
||||||
};
|
};
|
||||||
|
|
||||||
@Injectable()
|
@Injectable({providedIn: 'root'})
|
||||||
export class CalendarService {
|
export class CalendarService {
|
||||||
goToDate = new Subject<number>();
|
goToDate = new Subject<number>();
|
||||||
|
|
||||||
goToDateClicked = this.goToDate.asObservable();
|
goToDateClicked = this.goToDate.asObservable();
|
||||||
|
|
||||||
calendarName = 'StApps';
|
get calendarName(): string {
|
||||||
|
return this.config.app.name ?? 'StApps';
|
||||||
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||||
constructor(readonly calendar: Calendar, private readonly configProvider: ConfigProvider) {
|
constructor(readonly calendar: Calendar, readonly config: ConfigProvider) {}
|
||||||
this.calendarName = (this.configProvider.getValue('name') as string) ?? 'StApps';
|
|
||||||
}
|
|
||||||
|
|
||||||
async createCalendar(): Promise<CalendarInfo | undefined> {
|
async createCalendar(): Promise<CalendarInfo | undefined> {
|
||||||
await this.calendar.createCalendar({
|
await this.calendar.createCalendar({
|
||||||
|
|||||||
@@ -1,27 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2019 StApps
|
|
||||||
* This program is free software: you can redistribute it and/or modify it
|
|
||||||
* under the terms of the GNU General Public License as published by the Free
|
|
||||||
* Software Foundation, version 3.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
|
||||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
|
||||||
* more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License along with
|
|
||||||
* this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
import {NgModule} from '@angular/core';
|
|
||||||
import {DataModule} from '../data/data.module';
|
|
||||||
import {StorageModule} from '../storage/storage.module';
|
|
||||||
import {ConfigProvider} from './config.provider';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* TODO
|
|
||||||
*/
|
|
||||||
@NgModule({
|
|
||||||
imports: [StorageModule, DataModule],
|
|
||||||
providers: [ConfigProvider],
|
|
||||||
})
|
|
||||||
export class ConfigModule {}
|
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/ban-types */
|
||||||
/*
|
/*
|
||||||
* Copyright (C) 2022 StApps
|
* Copyright (C) 2022 StApps
|
||||||
* This program is free software: you can redistribute it and/or modify it
|
* This program is free software: you can redistribute it and/or modify it
|
||||||
@@ -14,19 +15,17 @@
|
|||||||
*/
|
*/
|
||||||
import {Injectable} from '@angular/core';
|
import {Injectable} from '@angular/core';
|
||||||
import {Client} from '@openstapps/api';
|
import {Client} from '@openstapps/api';
|
||||||
import {SCAppConfiguration, SCIndexResponse} from '@openstapps/core';
|
|
||||||
import packageInfo from '@openstapps/core/package.json';
|
|
||||||
import {NGXLogger} from 'ngx-logger';
|
|
||||||
import {environment} from '../../../environments/environment';
|
|
||||||
import {StAppsWebHttpClient} from '../data/stapps-web-http-client.provider';
|
|
||||||
import {StorageProvider} from '../storage/storage.provider';
|
|
||||||
import {
|
import {
|
||||||
ConfigFetchError,
|
SCAppConfiguration,
|
||||||
ConfigInitError,
|
SCAuthorizationProvider,
|
||||||
ConfigValueNotAvailable,
|
SCBackendConfiguration,
|
||||||
SavedConfigNotAvailable,
|
SCIndexResponse,
|
||||||
WrongConfigVersionInStorage,
|
} from '@openstapps/core';
|
||||||
} from './errors';
|
import coreInfo from '@openstapps/core/package.json';
|
||||||
|
import {environment} from '../../../environments/environment';
|
||||||
|
import {StorageProvider} from '../storage/storage.provider';
|
||||||
|
import {BeforeAppInit} from '../../before-app-init';
|
||||||
|
import {StAppsWebHttpClient} from '../data/stapps-web-http-client.provider';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Key to store config in storage module
|
* Key to store config in storage module
|
||||||
@@ -35,145 +34,55 @@ import {
|
|||||||
*/
|
*/
|
||||||
export const STORAGE_KEY_CONFIG = 'stapps.config';
|
export const STORAGE_KEY_CONFIG = 'stapps.config';
|
||||||
|
|
||||||
/**
|
|
||||||
* Provides configuration
|
|
||||||
*/
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root',
|
providedIn: 'root',
|
||||||
})
|
})
|
||||||
export class ConfigProvider {
|
export class ConfigProvider implements SCIndexResponse, BeforeAppInit {
|
||||||
/**
|
private client: Client;
|
||||||
* Api client
|
|
||||||
*/
|
|
||||||
client: Client;
|
|
||||||
|
|
||||||
/**
|
constructor(private storageProvider: StorageProvider, httpClient: StAppsWebHttpClient) {
|
||||||
* App configuration as IndexResponse
|
this.client = new Client(httpClient, environment.backend_url, environment.backend_version);
|
||||||
*/
|
}
|
||||||
config: SCIndexResponse;
|
|
||||||
|
|
||||||
/**
|
async beforeAppInit() {
|
||||||
* Version of the @openstapps/core package that app is using
|
this.isFirstSession = !(await this.storageProvider.has(STORAGE_KEY_CONFIG));
|
||||||
*/
|
// Queue config update for next launch; don't block current launch
|
||||||
scVersion = packageInfo.version;
|
const configUpdate = this.updateConfig();
|
||||||
|
console.log('Config update queued');
|
||||||
|
|
||||||
/**
|
const config = await this.storageProvider
|
||||||
* First session indicator (config not found in storage)
|
.get<SCIndexResponse>(STORAGE_KEY_CONFIG)
|
||||||
*/
|
.then(it => it ?? configUpdate);
|
||||||
firstSession = true;
|
|
||||||
|
|
||||||
/**
|
Object.assign(this, config);
|
||||||
* Constructor, initialise api client
|
|
||||||
* @param storageProvider StorageProvider to load persistent configuration
|
console.assert(
|
||||||
* @param swHttpClient Api client
|
this.backend.SCVersion === coreInfo.version,
|
||||||
* @param logger An angular logger
|
'Wrong config version in storage.',
|
||||||
*/
|
'Expected:',
|
||||||
constructor(
|
coreInfo.version,
|
||||||
private readonly storageProvider: StorageProvider,
|
'Actual:',
|
||||||
swHttpClient: StAppsWebHttpClient,
|
this.backend.SCVersion,
|
||||||
private readonly logger: NGXLogger,
|
);
|
||||||
) {
|
|
||||||
this.client = new Client(swHttpClient, environment.backend_url, environment.backend_version);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetches configuration from backend
|
* Updates the config from remote
|
||||||
*/
|
*/
|
||||||
async fetch(): Promise<SCIndexResponse> {
|
async updateConfig(): Promise<SCIndexResponse> {
|
||||||
try {
|
const config = await this.client.handshake(coreInfo.version);
|
||||||
return await this.client.handshake(this.scVersion);
|
|
||||||
} catch {
|
|
||||||
throw new ConfigFetchError();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the value of an app configuration
|
|
||||||
* @param attribute requested attribute from app configuration
|
|
||||||
*/
|
|
||||||
public getValue(attribute: keyof SCAppConfiguration) {
|
|
||||||
if (this.config.app[attribute] !== undefined) {
|
|
||||||
return this.config.app[attribute];
|
|
||||||
}
|
|
||||||
throw new ConfigValueNotAvailable(attribute);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a value of the configuration (not only app configuration)
|
|
||||||
* @param attribute requested attribute from the configuration
|
|
||||||
*/
|
|
||||||
public getAnyValue(attribute: keyof SCIndexResponse) {
|
|
||||||
if (this.config[attribute] !== undefined) {
|
|
||||||
return this.config[attribute];
|
|
||||||
}
|
|
||||||
throw new ConfigValueNotAvailable(attribute);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initialises the ConfigProvider
|
|
||||||
* @throws ConfigInitError if no configuration could be loaded.
|
|
||||||
* @throws WrongConfigVersionInStorage if fetch failed and saved config has wrong SCVersion
|
|
||||||
*/
|
|
||||||
async init(): Promise<void> {
|
|
||||||
let loadError;
|
|
||||||
let fetchError;
|
|
||||||
// load saved configuration
|
|
||||||
try {
|
|
||||||
this.config = await this.loadLocal();
|
|
||||||
this.firstSession = false;
|
|
||||||
this.logger.log(`initialised configuration from storage`);
|
|
||||||
if (this.config.backend.SCVersion !== this.scVersion) {
|
|
||||||
loadError = new WrongConfigVersionInStorage(this.scVersion, this.config.backend.SCVersion);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
loadError = error;
|
|
||||||
}
|
|
||||||
// fetch remote configuration from backend
|
|
||||||
try {
|
|
||||||
const fetchedConfig: SCIndexResponse = await this.fetch();
|
|
||||||
await this.set(fetchedConfig);
|
|
||||||
this.logger.log(`initialised configuration from remote`);
|
|
||||||
} catch (error) {
|
|
||||||
fetchError = error;
|
|
||||||
}
|
|
||||||
// check for occurred errors and throw them
|
|
||||||
if (loadError !== undefined && fetchError !== undefined) {
|
|
||||||
throw new ConfigInitError();
|
|
||||||
}
|
|
||||||
if (loadError !== undefined) {
|
|
||||||
this.logger.warn(loadError);
|
|
||||||
}
|
|
||||||
if (fetchError !== undefined) {
|
|
||||||
this.logger.warn(fetchError);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns saved configuration from StorageModule
|
|
||||||
* @throws SavedConfigNotAvailable if no configuration could be loaded
|
|
||||||
*/
|
|
||||||
async loadLocal(): Promise<SCIndexResponse> {
|
|
||||||
// get local configuration
|
|
||||||
if (await this.storageProvider.has(STORAGE_KEY_CONFIG)) {
|
|
||||||
return this.storageProvider.get<SCIndexResponse>(STORAGE_KEY_CONFIG);
|
|
||||||
}
|
|
||||||
throw new SavedConfigNotAvailable();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Saves the configuration from the provider
|
|
||||||
* @param config configuration to save
|
|
||||||
*/
|
|
||||||
async save(config: SCIndexResponse): Promise<void> {
|
|
||||||
await this.storageProvider.put(STORAGE_KEY_CONFIG, config);
|
await this.storageProvider.put(STORAGE_KEY_CONFIG, config);
|
||||||
|
|
||||||
|
console.log(`Config updated`);
|
||||||
|
|
||||||
|
return config;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
app: SCAppConfiguration;
|
||||||
* Sets the configuration in the module and writes it into app storage
|
|
||||||
* @param config SCIndexResponse to set
|
auth: {default?: SCAuthorizationProvider | undefined; paia?: SCAuthorizationProvider | undefined};
|
||||||
*/
|
|
||||||
async set(config: SCIndexResponse): Promise<void> {
|
backend: SCBackendConfiguration;
|
||||||
this.config = config;
|
|
||||||
await this.save(this.config);
|
isFirstSession: boolean;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,65 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2022 StApps
|
|
||||||
* This program is free software: you can redistribute it and/or modify it
|
|
||||||
* under the terms of the GNU General Public License as published by the Free
|
|
||||||
* Software Foundation, version 3.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
|
||||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
|
||||||
* more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License along with
|
|
||||||
* this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import {AppError} from '../../_helpers/errors';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Error that is thrown when fetching from backend fails
|
|
||||||
*/
|
|
||||||
export class ConfigFetchError extends AppError {
|
|
||||||
constructor() {
|
|
||||||
super('ConfigFetchError', 'App configuration could not be fetched!');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Error that is thrown when the ConfigProvider could be initialised
|
|
||||||
*/
|
|
||||||
export class ConfigInitError extends AppError {
|
|
||||||
constructor() {
|
|
||||||
super('ConfigInitError', 'App configuration could not be initialised!');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Error that is thrown when the requested config value is not available
|
|
||||||
*/
|
|
||||||
export class ConfigValueNotAvailable extends AppError {
|
|
||||||
constructor(valueKey: string) {
|
|
||||||
super('ConfigValueNotAvailable', `No attribute "${valueKey}" in config available!`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Error that is thrown when no saved config is available
|
|
||||||
*/
|
|
||||||
export class SavedConfigNotAvailable extends AppError {
|
|
||||||
constructor() {
|
|
||||||
super('SavedConfigNotAvailable', 'No saved app configuration available.');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Error that is thrown when the SCVersion of the saved config is not compatible with the app
|
|
||||||
*/
|
|
||||||
export class WrongConfigVersionInStorage extends AppError {
|
|
||||||
constructor(correctVersion: string, savedVersion: string) {
|
|
||||||
super(
|
|
||||||
'WrongConfigVersionInStorage',
|
|
||||||
`The saved configs backend version ${savedVersion} ` +
|
|
||||||
`does not equal the configured backend version ${correctVersion} of the app.`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -30,9 +30,8 @@ export class OffersInListComponent {
|
|||||||
@Input() set offers(it: Array<SCThingThatCanBeOfferedOffer<SCAcademicPriceGroup>>) {
|
@Input() set offers(it: Array<SCThingThatCanBeOfferedOffer<SCAcademicPriceGroup>>) {
|
||||||
this._offers = it;
|
this._offers = it;
|
||||||
this.price = it[0].prices?.default;
|
this.price = it[0].prices?.default;
|
||||||
this.settingsProvider.getSetting('profile', 'group').then(group => {
|
const group = this.settingsProvider.getSetting('profile', 'group');
|
||||||
this.price = it[0].prices?.[(group.value as string).replace(/s$/, '') as never];
|
this.price = it[0].prices?.[(group.value as string).replace(/s$/, '') as never];
|
||||||
});
|
|
||||||
|
|
||||||
const availabilities = new Set(it.map(offer => offer.availability));
|
const availabilities = new Set(it.map(offer => offer.availability));
|
||||||
this.soldOut = availabilities.has('out of stock') && availabilities.size === 1;
|
this.soldOut = availabilities.has('out of stock') && availabilities.size === 1;
|
||||||
|
|||||||
@@ -15,16 +15,9 @@
|
|||||||
import {Component, DestroyRef, inject, Input, OnInit} from '@angular/core';
|
import {Component, DestroyRef, inject, Input, OnInit} from '@angular/core';
|
||||||
import {ActivatedRoute, Router} from '@angular/router';
|
import {ActivatedRoute, Router} from '@angular/router';
|
||||||
import {Keyboard} from '@capacitor/keyboard';
|
import {Keyboard} from '@capacitor/keyboard';
|
||||||
import {AlertController, AnimationBuilder, AnimationController} from '@ionic/angular';
|
import {AlertController, AnimationController} from '@ionic/angular';
|
||||||
import {Capacitor} from '@capacitor/core';
|
import {Capacitor} from '@capacitor/core';
|
||||||
import {
|
import {SCFacet, SCSearchFilter, SCSearchQuery, SCSearchSort, SCThings} from '@openstapps/core';
|
||||||
SCFacet,
|
|
||||||
SCFeatureConfiguration,
|
|
||||||
SCSearchFilter,
|
|
||||||
SCSearchQuery,
|
|
||||||
SCSearchSort,
|
|
||||||
SCThings,
|
|
||||||
} from '@openstapps/core';
|
|
||||||
import {NGXLogger} from 'ngx-logger';
|
import {NGXLogger} from 'ngx-logger';
|
||||||
import {combineLatest, Subject} from 'rxjs';
|
import {combineLatest, Subject} from 'rxjs';
|
||||||
import {debounceTime, distinctUntilChanged, startWith} from 'rxjs/operators';
|
import {debounceTime, distinctUntilChanged, startWith} from 'rxjs/operators';
|
||||||
@@ -33,9 +26,9 @@ import {SettingsProvider} from '../../settings/settings.provider';
|
|||||||
import {DataRoutingService} from '../data-routing.service';
|
import {DataRoutingService} from '../data-routing.service';
|
||||||
import {DataProvider} from '../data.provider';
|
import {DataProvider} from '../data.provider';
|
||||||
import {PositionService} from '../../map/position.service';
|
import {PositionService} from '../../map/position.service';
|
||||||
import {ConfigProvider} from '../../config/config.provider';
|
|
||||||
import {searchPageSwitchAnimation} from './search-page-switch-animation';
|
import {searchPageSwitchAnimation} from './search-page-switch-animation';
|
||||||
import {takeUntilDestroyed} from '@angular/core/rxjs-interop';
|
import {takeUntilDestroyed} from '@angular/core/rxjs-interop';
|
||||||
|
import {ConfigProvider} from '../../config/config.provider';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* SearchPageComponent queries things and shows list of things as search results and filter as context menu
|
* SearchPageComponent queries things and shows list of things as search results and filter as context menu
|
||||||
@@ -144,21 +137,8 @@ export class SearchPageComponent implements OnInit {
|
|||||||
|
|
||||||
destroy$ = inject(DestroyRef);
|
destroy$ = inject(DestroyRef);
|
||||||
|
|
||||||
routeAnimation: AnimationBuilder;
|
routeAnimation = searchPageSwitchAnimation(inject(AnimationController));
|
||||||
|
|
||||||
/**
|
|
||||||
* Injects the providers and creates subscriptions
|
|
||||||
* @param alertController AlertController
|
|
||||||
* @param dataProvider DataProvider
|
|
||||||
* @param contextMenuService ContextMenuService
|
|
||||||
* @param settingsProvider SettingsProvider
|
|
||||||
* @param logger An angular logger
|
|
||||||
* @param dataRoutingService DataRoutingService
|
|
||||||
* @param router Router
|
|
||||||
* @param route ActivatedRoute
|
|
||||||
* @param positionService PositionService
|
|
||||||
* @param configProvider ConfigProvider
|
|
||||||
*/
|
|
||||||
constructor(
|
constructor(
|
||||||
protected readonly alertController: AlertController,
|
protected readonly alertController: AlertController,
|
||||||
protected dataProvider: DataProvider,
|
protected dataProvider: DataProvider,
|
||||||
@@ -169,11 +149,8 @@ export class SearchPageComponent implements OnInit {
|
|||||||
protected router: Router,
|
protected router: Router,
|
||||||
private readonly route: ActivatedRoute,
|
private readonly route: ActivatedRoute,
|
||||||
protected positionService: PositionService,
|
protected positionService: PositionService,
|
||||||
private readonly configProvider: ConfigProvider,
|
private readonly config: ConfigProvider,
|
||||||
animationController: AnimationController,
|
) {}
|
||||||
) {
|
|
||||||
this.routeAnimation = searchPageSwitchAnimation(animationController);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetches items with set query configuration
|
* Fetches items with set query configuration
|
||||||
@@ -347,8 +324,7 @@ export class SearchPageComponent implements OnInit {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const features = this.configProvider.getValue('features') as SCFeatureConfiguration;
|
this.isHebisAvailable = !!this.config.app.features.plugins?.['hebis-plugin']?.urlPath;
|
||||||
this.isHebisAvailable = !!features.plugins?.['hebis-plugin']?.urlPath;
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.logger.error(error);
|
this.logger.error(error);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -62,10 +62,8 @@ export class RatingProvider {
|
|||||||
return new Date(today.getFullYear(), today.getMonth(), today.getDate()).toISOString();
|
return new Date(today.getFullYear(), today.getMonth(), today.getDate()).toISOString();
|
||||||
}
|
}
|
||||||
|
|
||||||
private get userGroup(): Promise<SCUserGroup> {
|
private get userGroup(): SCUserGroup {
|
||||||
return this.settingsProvider
|
return (this.settingsProvider.getSetting('profile', 'group') as SCUserGroupSetting).value as SCUserGroup;
|
||||||
.getSetting('profile', 'group')
|
|
||||||
.then(it => (it as SCUserGroupSetting).value as SCUserGroup);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async getStoredRatings(): Promise<RatingStorage> {
|
private async getStoredRatings(): Promise<RatingStorage> {
|
||||||
|
|||||||
@@ -12,21 +12,13 @@
|
|||||||
* You should have received a copy of the GNU General Public License along with
|
* You should have received a copy of the GNU General Public License along with
|
||||||
* this program. If not, see <https://www.gnu.org/licenses/>.
|
* this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
import {Component, OnInit} from '@angular/core';
|
import {Component, inject, OnInit} from '@angular/core';
|
||||||
import {AlertController, AnimationController} from '@ionic/angular';
|
|
||||||
import {ActivatedRoute, Router} from '@angular/router';
|
|
||||||
import {NGXLogger} from 'ngx-logger';
|
|
||||||
import {debounceTime, distinctUntilChanged, startWith, take} from 'rxjs/operators';
|
import {debounceTime, distinctUntilChanged, startWith, take} from 'rxjs/operators';
|
||||||
import {combineLatest} from 'rxjs';
|
import {combineLatest} from 'rxjs';
|
||||||
import {SCThingType} from '@openstapps/core';
|
import {SCThingType} from '@openstapps/core';
|
||||||
import {FavoritesService} from './favorites.service';
|
import {FavoritesService} from './favorites.service';
|
||||||
import {DataRoutingService} from '../data/data-routing.service';
|
|
||||||
import {ContextMenuService} from '../menu/context/context-menu.service';
|
import {ContextMenuService} from '../menu/context/context-menu.service';
|
||||||
import {SearchPageComponent} from '../data/list/search-page.component';
|
import {SearchPageComponent} from '../data/list/search-page.component';
|
||||||
import {DataProvider} from '../data/data.provider';
|
|
||||||
import {SettingsProvider} from '../settings/settings.provider';
|
|
||||||
import {PositionService} from '../map/position.service';
|
|
||||||
import {ConfigProvider} from '../config/config.provider';
|
|
||||||
import {takeUntilDestroyed} from '@angular/core/rxjs-interop';
|
import {takeUntilDestroyed} from '@angular/core/rxjs-interop';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -42,34 +34,7 @@ export class FavoritesPageComponent extends SearchPageComponent implements OnIni
|
|||||||
|
|
||||||
showNavigation = false;
|
showNavigation = false;
|
||||||
|
|
||||||
constructor(
|
favoritesService = inject(FavoritesService);
|
||||||
alertController: AlertController,
|
|
||||||
dataProvider: DataProvider,
|
|
||||||
contextMenuService: ContextMenuService,
|
|
||||||
settingsProvider: SettingsProvider,
|
|
||||||
logger: NGXLogger,
|
|
||||||
dataRoutingService: DataRoutingService,
|
|
||||||
router: Router,
|
|
||||||
route: ActivatedRoute,
|
|
||||||
positionService: PositionService,
|
|
||||||
private favoritesService: FavoritesService,
|
|
||||||
configProvider: ConfigProvider,
|
|
||||||
animationController: AnimationController,
|
|
||||||
) {
|
|
||||||
super(
|
|
||||||
alertController,
|
|
||||||
dataProvider,
|
|
||||||
contextMenuService,
|
|
||||||
settingsProvider,
|
|
||||||
logger,
|
|
||||||
dataRoutingService,
|
|
||||||
router,
|
|
||||||
route,
|
|
||||||
positionService,
|
|
||||||
configProvider,
|
|
||||||
animationController,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
super.ngOnInit(false);
|
super.ngOnInit(false);
|
||||||
|
|||||||
@@ -16,10 +16,9 @@ import {Injectable} from '@angular/core';
|
|||||||
import {DaiaAvailabilityResponse, DaiaHolding, DaiaService} from './protocol/response';
|
import {DaiaAvailabilityResponse, DaiaHolding, DaiaService} from './protocol/response';
|
||||||
import {StorageProvider} from '../storage/storage.provider';
|
import {StorageProvider} from '../storage/storage.provider';
|
||||||
import {HttpClient, HttpHeaders} from '@angular/common/http';
|
import {HttpClient, HttpHeaders} from '@angular/common/http';
|
||||||
import {ConfigProvider} from '../config/config.provider';
|
|
||||||
import {SCFeatureConfiguration} from '@openstapps/core';
|
|
||||||
import {NGXLogger} from 'ngx-logger';
|
import {NGXLogger} from 'ngx-logger';
|
||||||
import {TranslateService} from '@ngx-translate/core';
|
import {TranslateService} from '@ngx-translate/core';
|
||||||
|
import {ConfigProvider} from '../config/config.provider';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generated class for the DataProvider provider.
|
* Generated class for the DataProvider provider.
|
||||||
@@ -44,18 +43,10 @@ export class DaiaDataProvider {
|
|||||||
|
|
||||||
clientHeaders = new HttpHeaders();
|
clientHeaders = new HttpHeaders();
|
||||||
|
|
||||||
/**
|
|
||||||
* TODO
|
|
||||||
* @param storageProvider TODO
|
|
||||||
* @param httpClient TODO
|
|
||||||
* @param configProvider TODO
|
|
||||||
* @param logger TODO
|
|
||||||
* @param translateService TODO
|
|
||||||
*/
|
|
||||||
constructor(
|
constructor(
|
||||||
storageProvider: StorageProvider,
|
storageProvider: StorageProvider,
|
||||||
httpClient: HttpClient,
|
httpClient: HttpClient,
|
||||||
private configProvider: ConfigProvider,
|
private config: ConfigProvider,
|
||||||
private readonly logger: NGXLogger,
|
private readonly logger: NGXLogger,
|
||||||
private translateService: TranslateService,
|
private translateService: TranslateService,
|
||||||
) {
|
) {
|
||||||
@@ -67,15 +58,14 @@ export class DaiaDataProvider {
|
|||||||
async getAvailability(id: string): Promise<DaiaHolding[] | undefined> {
|
async getAvailability(id: string): Promise<DaiaHolding[] | undefined> {
|
||||||
if (this.daiaServiceUrl === undefined) {
|
if (this.daiaServiceUrl === undefined) {
|
||||||
try {
|
try {
|
||||||
const features = this.configProvider.getValue('features') as SCFeatureConfiguration;
|
if (this.config.app.features.extern?.daia?.url) {
|
||||||
if (features.extern?.daia?.url) {
|
this.daiaServiceUrl = this.config.app.features.extern?.daia?.url;
|
||||||
this.daiaServiceUrl = features.extern?.daia?.url;
|
|
||||||
} else {
|
} else {
|
||||||
this.logger.error('Daia service url undefined');
|
this.logger.error('Daia service url undefined');
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
if (features.extern?.hebisProxy?.url) {
|
if (this.config.app.features.extern?.hebisProxy?.url) {
|
||||||
this.hebisProxyUrl = features.extern?.hebisProxy?.url;
|
this.hebisProxyUrl = this.config.app.features.extern?.hebisProxy?.url;
|
||||||
} else {
|
} else {
|
||||||
this.logger.error('HeBIS proxy url undefined');
|
this.logger.error('HeBIS proxy url undefined');
|
||||||
return undefined;
|
return undefined;
|
||||||
|
|||||||
@@ -12,22 +12,17 @@
|
|||||||
* You should have received a copy of the GNU General Public License along with
|
* You should have received a copy of the GNU General Public License along with
|
||||||
* this program. If not, see <https://www.gnu.org/licenses/>.
|
* this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {Injectable} from '@angular/core';
|
import {Injectable} from '@angular/core';
|
||||||
import {JQueryRequestor, Requestor} from '@openid/appauth';
|
import {JQueryRequestor, Requestor} from '@openid/appauth';
|
||||||
import {
|
import {SCAuthorizationProviderType} from '@openstapps/core';
|
||||||
SCAuthorizationProviderType,
|
|
||||||
SCFeatureConfiguration,
|
|
||||||
SCFeatureConfigurationExtern,
|
|
||||||
} from '@openstapps/core';
|
|
||||||
import {DocumentAction, PAIADocument, PAIADocumentStatus, PAIAFees, PAIAItems, PAIAPatron} from '../types';
|
import {DocumentAction, PAIADocument, PAIADocumentStatus, PAIAFees, PAIAItems, PAIAPatron} from '../types';
|
||||||
import {HebisDataProvider} from '../../hebis/hebis-data.provider';
|
import {HebisDataProvider} from '../../hebis/hebis-data.provider';
|
||||||
import {PAIATokenResponse} from '../../auth/paia/paia-token-response';
|
import {PAIATokenResponse} from '../../auth/paia/paia-token-response';
|
||||||
import {AuthHelperService} from '../../auth/auth-helper.service';
|
import {AuthHelperService} from '../../auth/auth-helper.service';
|
||||||
import {ConfigProvider} from '../../config/config.provider';
|
|
||||||
import {TranslateService} from '@ngx-translate/core';
|
import {TranslateService} from '@ngx-translate/core';
|
||||||
import {AlertController, ToastController} from '@ionic/angular';
|
import {AlertController, ToastController} from '@ionic/angular';
|
||||||
import {HebisSearchResponse} from '../../hebis/protocol/response';
|
import {HebisSearchResponse} from '../../hebis/protocol/response';
|
||||||
|
import {ConfigProvider} from '../../config/config.provider';
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root',
|
providedIn: 'root',
|
||||||
@@ -36,29 +31,26 @@ export class LibraryAccountService {
|
|||||||
/**
|
/**
|
||||||
* Base url of the external service
|
* Base url of the external service
|
||||||
*/
|
*/
|
||||||
baseUrl: string;
|
get baseUrl(): string {
|
||||||
|
return this.config.app.features.extern!['paia'].url;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Authorization provider type
|
* Authorization provider type
|
||||||
*/
|
*/
|
||||||
authType: SCAuthorizationProviderType;
|
get authType(): SCAuthorizationProviderType {
|
||||||
|
return this.config.app.features.extern!['paia'].authProvider!;
|
||||||
|
}
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
protected requestor: Requestor = new JQueryRequestor(),
|
protected requestor: Requestor = new JQueryRequestor(),
|
||||||
private readonly hebisDataProvider: HebisDataProvider,
|
private readonly hebisDataProvider: HebisDataProvider,
|
||||||
private readonly authHelper: AuthHelperService,
|
private readonly authHelper: AuthHelperService,
|
||||||
readonly configProvider: ConfigProvider,
|
|
||||||
private readonly translateService: TranslateService,
|
private readonly translateService: TranslateService,
|
||||||
private readonly alertController: AlertController,
|
private readonly alertController: AlertController,
|
||||||
private readonly toastController: ToastController,
|
private readonly toastController: ToastController,
|
||||||
) {
|
private readonly config: ConfigProvider,
|
||||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
) {}
|
||||||
const config: SCFeatureConfigurationExtern = (
|
|
||||||
configProvider.getValue('features') as SCFeatureConfiguration
|
|
||||||
).extern!.paia;
|
|
||||||
this.baseUrl = config.url;
|
|
||||||
this.authType = config.authProvider as SCAuthorizationProviderType;
|
|
||||||
}
|
|
||||||
|
|
||||||
async getProfile() {
|
async getProfile() {
|
||||||
const patron = ((await this.getValidToken()) as PAIATokenResponse).patron;
|
const patron = ((await this.getValidToken()) as PAIATokenResponse).patron;
|
||||||
|
|||||||
@@ -19,9 +19,7 @@ import {LeafletModule} from '@asymmetrik/ngx-leaflet';
|
|||||||
import {LeafletMarkerClusterModule} from '@asymmetrik/ngx-leaflet-markercluster';
|
import {LeafletMarkerClusterModule} from '@asymmetrik/ngx-leaflet-markercluster';
|
||||||
import {IonicModule} from '@ionic/angular';
|
import {IonicModule} from '@ionic/angular';
|
||||||
import {TranslateModule} from '@ngx-translate/core';
|
import {TranslateModule} from '@ngx-translate/core';
|
||||||
import {Polygon} from 'geojson';
|
|
||||||
import {ThingTranslateModule} from '../../translation/thing-translate.module';
|
import {ThingTranslateModule} from '../../translation/thing-translate.module';
|
||||||
import {ConfigProvider} from '../config/config.provider';
|
|
||||||
import {DataFacetsProvider} from '../data/data-facets.provider';
|
import {DataFacetsProvider} from '../data/data-facets.provider';
|
||||||
import {DataModule} from '../data/data.module';
|
import {DataModule} from '../data/data.module';
|
||||||
import {DataProvider} from '../data/data.provider';
|
import {DataProvider} from '../data/data.provider';
|
||||||
@@ -35,17 +33,6 @@ import {UtilModule} from '../../util/util.module';
|
|||||||
import {IonIconModule} from '../../util/ion-icon/ion-icon.module';
|
import {IonIconModule} from '../../util/ion-icon/ion-icon.module';
|
||||||
import {GeoNavigationDirective} from './geo-navigation.directive';
|
import {GeoNavigationDirective} from './geo-navigation.directive';
|
||||||
|
|
||||||
/**
|
|
||||||
* Initializes the default area to show in advance (before components are initialized)
|
|
||||||
* @param configProvider An instance of the ConfigProvider to read the campus polygon from
|
|
||||||
* @param mapProvider An instance of the MapProvider to set the default polygon (area to show on the map)
|
|
||||||
*/
|
|
||||||
export function initMapConfigFactory(configProvider: ConfigProvider, mapProvider: MapProvider) {
|
|
||||||
return async () => {
|
|
||||||
mapProvider.defaultPolygon = (await configProvider.getValue('campusPolygon')) as Polygon;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const mapRoutes: Routes = [
|
const mapRoutes: Routes = [
|
||||||
{path: 'map', component: MapPageComponent},
|
{path: 'map', component: MapPageComponent},
|
||||||
{path: 'map/:uid', component: MapPageComponent},
|
{path: 'map/:uid', component: MapPageComponent},
|
||||||
|
|||||||
@@ -21,12 +21,11 @@ import {
|
|||||||
SCThingType,
|
SCThingType,
|
||||||
SCUuid,
|
SCUuid,
|
||||||
} from '@openstapps/core';
|
} from '@openstapps/core';
|
||||||
import {Point, Polygon} from 'geojson';
|
import {Point} from 'geojson';
|
||||||
import {divIcon, geoJSON, LatLng, Map, marker, Marker} from 'leaflet';
|
import {divIcon, geoJSON, LatLng, Map, marker, Marker} from 'leaflet';
|
||||||
import {DataProvider} from '../data/data.provider';
|
import {DataProvider} from '../data/data.provider';
|
||||||
import {MapPosition, PositionService} from './position.service';
|
import {MapPosition, PositionService} from './position.service';
|
||||||
import {hasValidLocation} from '../data/types/place/place-types';
|
import {hasValidLocation} from '../data/types/place/place-types';
|
||||||
import {ConfigProvider} from '../config/config.provider';
|
|
||||||
import {SCIcon} from '../../util/ion-icon/icon';
|
import {SCIcon} from '../../util/ion-icon/icon';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -36,11 +35,6 @@ import {SCIcon} from '../../util/ion-icon/icon';
|
|||||||
providedIn: 'root',
|
providedIn: 'root',
|
||||||
})
|
})
|
||||||
export class MapProvider {
|
export class MapProvider {
|
||||||
/**
|
|
||||||
* Area to show when the map is initialized (shown for the first time)
|
|
||||||
*/
|
|
||||||
defaultPolygon: Polygon;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provide a point marker for a leaflet map
|
* Provide a point marker for a leaflet map
|
||||||
* @param point Point to get marker for
|
* @param point Point to get marker for
|
||||||
@@ -111,13 +105,7 @@ export class MapProvider {
|
|||||||
clearInterval(interval);
|
clearInterval(interval);
|
||||||
};
|
};
|
||||||
|
|
||||||
constructor(
|
constructor(private dataProvider: DataProvider, private positionService: PositionService) {}
|
||||||
private dataProvider: DataProvider,
|
|
||||||
private positionService: PositionService,
|
|
||||||
private configProvider: ConfigProvider,
|
|
||||||
) {
|
|
||||||
this.defaultPolygon = this.configProvider.getValue('campusPolygon') as Polygon;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provide the specific place by its UID
|
* Provide the specific place by its UID
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ import {Capacitor} from '@capacitor/core';
|
|||||||
import {pauseWhen} from '../../../util/rxjs/pause-when';
|
import {pauseWhen} from '../../../util/rxjs/pause-when';
|
||||||
import {takeUntilDestroyed} from '@angular/core/rxjs-interop';
|
import {takeUntilDestroyed} from '@angular/core/rxjs-interop';
|
||||||
import {startViewTransition} from '../../../util/view-transition';
|
import {startViewTransition} from '../../../util/view-transition';
|
||||||
|
import {ConfigProvider} from '../../config/config.provider';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The main page of the map
|
* The main page of the map
|
||||||
@@ -102,7 +103,7 @@ export class MapPageComponent implements OnInit {
|
|||||||
* Options of the leaflet map
|
* Options of the leaflet map
|
||||||
*/
|
*/
|
||||||
options: MapOptions = {
|
options: MapOptions = {
|
||||||
center: geoJSON(this.mapProvider.defaultPolygon).getBounds().getCenter(),
|
center: geoJSON(inject(ConfigProvider).app.campusPolygon).getBounds().getCenter(),
|
||||||
layers: [
|
layers: [
|
||||||
tileLayer('https://osm.server.uni-frankfurt.de/tiles/roads/x={x}&y={y}&z={z}', {
|
tileLayer('https://osm.server.uni-frankfurt.de/tiles/roads/x={x}&y={y}&z={z}', {
|
||||||
attribution: '© <a href="http://osm.org/copyright">OpenStreetMap</a> contributors',
|
attribution: '© <a href="http://osm.org/copyright">OpenStreetMap</a> contributors',
|
||||||
|
|||||||
@@ -12,75 +12,23 @@
|
|||||||
* You should have received a copy of the GNU General Public License along with
|
* You should have received a copy of the GNU General Public License along with
|
||||||
* this program. If not, see <https://www.gnu.org/licenses/>.
|
* this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
import {Component, OnInit} from '@angular/core';
|
import {Component, inject} from '@angular/core';
|
||||||
import {LangChangeEvent, TranslateService} from '@ngx-translate/core';
|
|
||||||
import {
|
|
||||||
SCAppConfigurationMenuCategory,
|
|
||||||
SCLanguage,
|
|
||||||
SCThingTranslator,
|
|
||||||
SCTranslations,
|
|
||||||
} from '@openstapps/core';
|
|
||||||
import {NavigationService} from './navigation.service';
|
|
||||||
import config from 'capacitor.config';
|
|
||||||
import {SettingsProvider} from '../../settings/settings.provider';
|
|
||||||
import {BreakpointObserver} from '@angular/cdk/layout';
|
import {BreakpointObserver} from '@angular/cdk/layout';
|
||||||
|
import {map} from 'rxjs/operators';
|
||||||
|
import {ConfigProvider} from '../../config/config.provider';
|
||||||
|
|
||||||
/**
|
|
||||||
* Generated class for the MenuPage page.
|
|
||||||
*
|
|
||||||
* See https://ionicframework.com/docs/components/#navigation for more info on
|
|
||||||
* Ionic pages and navigation.
|
|
||||||
*/
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'stapps-navigation',
|
selector: 'stapps-navigation',
|
||||||
styleUrls: ['navigation.scss'],
|
styleUrls: ['navigation.scss'],
|
||||||
templateUrl: 'navigation.html',
|
templateUrl: 'navigation.html',
|
||||||
})
|
})
|
||||||
export class NavigationComponent implements OnInit {
|
export class NavigationComponent {
|
||||||
showTabbar = true;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Name of the app
|
* TODO: What was this for???
|
||||||
*/
|
*/
|
||||||
appName = config.appName;
|
showTabBar$ = inject(BreakpointObserver)
|
||||||
|
.observe(['(min-width: 768px)'])
|
||||||
|
.pipe(map(({matches}) => !matches));
|
||||||
|
|
||||||
/**
|
constructor(readonly config: ConfigProvider) {}
|
||||||
* Possible languages to be used for translation
|
|
||||||
*/
|
|
||||||
language: keyof SCTranslations<SCLanguage>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Menu entries from config module
|
|
||||||
*/
|
|
||||||
menu: SCAppConfigurationMenuCategory[];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Core translator
|
|
||||||
*/
|
|
||||||
translator: SCThingTranslator;
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
public translateService: TranslateService,
|
|
||||||
private navigationService: NavigationService,
|
|
||||||
private settingsProvider: SettingsProvider,
|
|
||||||
private responsive: BreakpointObserver,
|
|
||||||
) {
|
|
||||||
translateService.onLangChange.subscribe((event: LangChangeEvent) => {
|
|
||||||
this.language = event.lang as keyof SCTranslations<SCLanguage>;
|
|
||||||
this.translator = new SCThingTranslator(this.language);
|
|
||||||
});
|
|
||||||
|
|
||||||
this.responsive.observe(['(min-width: 768px)']).subscribe(result => {
|
|
||||||
this.showTabbar = !result.matches;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async ngOnInit() {
|
|
||||||
this.language = (await this.settingsProvider.getValue(
|
|
||||||
'profile',
|
|
||||||
'language',
|
|
||||||
)) as keyof SCTranslations<SCLanguage>;
|
|
||||||
this.translator = new SCThingTranslator(this.language);
|
|
||||||
this.menu = await this.navigationService.getMenu();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,8 +24,8 @@
|
|||||||
</ion-title>
|
</ion-title>
|
||||||
</ion-toolbar>
|
</ion-toolbar>
|
||||||
</ion-header>
|
</ion-header>
|
||||||
<ion-content *ngIf="menu">
|
<ion-content *ngIf="config.app.menus">
|
||||||
<ion-list *ngFor="let category of menu; first as isFirst">
|
<ion-list *ngFor="let category of config.app.menus; first as isFirst">
|
||||||
<ion-item
|
<ion-item
|
||||||
*ngIf="category.title !== ''"
|
*ngIf="category.title !== ''"
|
||||||
[rootLink]="category.route"
|
[rootLink]="category.route"
|
||||||
@@ -34,11 +34,11 @@
|
|||||||
class="menu-category"
|
class="menu-category"
|
||||||
>
|
>
|
||||||
<ion-icon slot="end" [name]="category.icon"></ion-icon>
|
<ion-icon slot="end" [name]="category.icon"></ion-icon>
|
||||||
<ion-label> {{ category.translations[language].title | titlecase }} </ion-label>
|
<ion-label> {{ 'title' | translateSimple: category | titlecase }} </ion-label>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
<ion-item *ngFor="let item of category.items" [rootLink]="item.route" [redirectedFrom]="item.route">
|
<ion-item *ngFor="let item of category.items" [rootLink]="item.route" [redirectedFrom]="item.route">
|
||||||
<ion-icon slot="end" [name]="item.icon"></ion-icon>
|
<ion-icon slot="end" [name]="item.icon"></ion-icon>
|
||||||
<ion-label> {{ item.translations[language].title | titlecase }} </ion-label>
|
<ion-label> {{ 'title' | translateSimple: item | titlecase }} </ion-label>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
</ion-list>
|
</ion-list>
|
||||||
</ion-content>
|
</ion-content>
|
||||||
|
|||||||
@@ -22,10 +22,11 @@ import {IonIconModule} from '../../../util/ion-icon/ion-icon.module';
|
|||||||
import {TranslateModule} from '@ngx-translate/core';
|
import {TranslateModule} from '@ngx-translate/core';
|
||||||
import {RouterModule} from '@angular/router';
|
import {RouterModule} from '@angular/router';
|
||||||
import {OfflineNoticeComponent} from './offline-notice.component';
|
import {OfflineNoticeComponent} from './offline-notice.component';
|
||||||
|
import {ThingTranslateModule} from '../../../translation/thing-translate.module';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
declarations: [RootLinkDirective, NavigationComponent, TabsComponent, OfflineNoticeComponent],
|
declarations: [RootLinkDirective, NavigationComponent, TabsComponent, OfflineNoticeComponent],
|
||||||
imports: [CommonModule, IonicModule, IonIconModule, TranslateModule, RouterModule],
|
imports: [CommonModule, IonicModule, IonIconModule, TranslateModule, RouterModule, ThingTranslateModule],
|
||||||
exports: [TabsComponent, RootLinkDirective, NavigationComponent],
|
exports: [TabsComponent, RootLinkDirective, NavigationComponent],
|
||||||
})
|
})
|
||||||
export class NavigationModule {}
|
export class NavigationModule {}
|
||||||
|
|||||||
@@ -1,37 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2022 StApps
|
|
||||||
* This program is free software: you can redistribute it and/or modify it
|
|
||||||
* under the terms of the GNU General Public License as published by the Free
|
|
||||||
* Software Foundation, version 3.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
|
||||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
|
||||||
* more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License along with
|
|
||||||
* this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import {Injectable} from '@angular/core';
|
|
||||||
import {SCAppConfigurationMenuCategory} from '@openstapps/core';
|
|
||||||
import {ConfigProvider} from '../../config/config.provider';
|
|
||||||
import {NGXLogger} from 'ngx-logger';
|
|
||||||
|
|
||||||
@Injectable({
|
|
||||||
providedIn: 'root',
|
|
||||||
})
|
|
||||||
export class NavigationService {
|
|
||||||
constructor(private configProvider: ConfigProvider, private logger: NGXLogger) {}
|
|
||||||
|
|
||||||
async getMenu() {
|
|
||||||
let menu: SCAppConfigurationMenuCategory[] = [];
|
|
||||||
try {
|
|
||||||
menu = this.configProvider.getValue('menus') as SCAppConfigurationMenuCategory[];
|
|
||||||
} catch (error) {
|
|
||||||
this.logger.error(`error from loading menu entries: ${error}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
return menu;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -12,93 +12,44 @@
|
|||||||
* You should have received a copy of the GNU General Public License along with
|
* You should have received a copy of the GNU General Public License along with
|
||||||
* this program. If not, see <https://www.gnu.org/licenses/>.
|
* this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
import {ChangeDetectionStrategy, Component, inject} from '@angular/core';
|
||||||
import {Component} from '@angular/core';
|
import {Event, NavigationEnd, Router} from '@angular/router';
|
||||||
import {NavigationEnd, Router} from '@angular/router';
|
import {SCAppConfigurationMenuCategory} from '@openstapps/core';
|
||||||
import {
|
|
||||||
SCAppConfigurationMenuCategory,
|
|
||||||
SCLanguage,
|
|
||||||
SCThingTranslator,
|
|
||||||
SCTranslations,
|
|
||||||
} from '@openstapps/core';
|
|
||||||
import {ConfigProvider} from '../../config/config.provider';
|
import {ConfigProvider} from '../../config/config.provider';
|
||||||
import {LangChangeEvent, TranslateService} from '@ngx-translate/core';
|
import {filter} from 'rxjs';
|
||||||
import {NGXLogger} from 'ngx-logger';
|
import {map, startWith} from 'rxjs/operators';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finds a tab name based on urls
|
||||||
|
*/
|
||||||
|
function findTabFromUrl(url: string, menus: SCAppConfigurationMenuCategory[]): string {
|
||||||
|
if (url === '/') {
|
||||||
|
return menus[0]?.title ?? '';
|
||||||
|
}
|
||||||
|
return menus.find(category => url.includes(category.route))?.title ?? '';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Type guard for event
|
||||||
|
*/
|
||||||
|
function isNavigationEnd(event: Event): event is NavigationEnd {
|
||||||
|
return event instanceof NavigationEnd;
|
||||||
|
}
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'stapps-navigation-tabs',
|
selector: 'stapps-navigation-tabs',
|
||||||
templateUrl: 'tabs.template.html',
|
templateUrl: 'tabs.html',
|
||||||
styleUrls: ['./tabs.component.scss'],
|
styleUrls: ['./tabs.scss'],
|
||||||
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
})
|
})
|
||||||
export class TabsComponent {
|
export class TabsComponent {
|
||||||
/**
|
menus = inject(ConfigProvider).app.menus.slice(0, 5);
|
||||||
* Possible languages to be used for translation
|
|
||||||
*/
|
|
||||||
language: keyof SCTranslations<SCLanguage>;
|
|
||||||
|
|
||||||
/**
|
selectedTab$ = this.router.events.pipe(
|
||||||
* Menu entries from config module
|
filter(isNavigationEnd),
|
||||||
*/
|
map(event => findTabFromUrl(event.url, this.menus)),
|
||||||
menu: SCAppConfigurationMenuCategory[];
|
startWith(findTabFromUrl(this.router.url, this.menus)),
|
||||||
|
);
|
||||||
|
|
||||||
/**
|
constructor(private readonly router: Router) {}
|
||||||
* Core translator
|
|
||||||
*/
|
|
||||||
translator: SCThingTranslator;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Name of selected tab
|
|
||||||
*/
|
|
||||||
selectedTab: string;
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
private readonly configProvider: ConfigProvider,
|
|
||||||
public translateService: TranslateService,
|
|
||||||
private readonly logger: NGXLogger,
|
|
||||||
private readonly router: Router,
|
|
||||||
) {
|
|
||||||
this.language = this.translateService.currentLang as keyof SCTranslations<SCLanguage>;
|
|
||||||
this.translator = new SCThingTranslator(this.language);
|
|
||||||
void this.loadMenuEntries();
|
|
||||||
this.router.events.subscribe((event: unknown) => {
|
|
||||||
if (event instanceof NavigationEnd) {
|
|
||||||
this.selectTab(event.url);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
this.selectTab(router.url);
|
|
||||||
|
|
||||||
translateService.onLangChange?.subscribe((event: LangChangeEvent) => {
|
|
||||||
this.language = event.lang as keyof SCTranslations<SCLanguage>;
|
|
||||||
this.translator = new SCThingTranslator(this.language);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Loads menu entries from configProvider
|
|
||||||
*/
|
|
||||||
async loadMenuEntries() {
|
|
||||||
try {
|
|
||||||
const menus = (await this.configProvider.getValue('menus')) as SCAppConfigurationMenuCategory[];
|
|
||||||
|
|
||||||
const menu = menus.slice(0, 5);
|
|
||||||
if (menu) {
|
|
||||||
this.menu = menu;
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
this.logger.error(`error from loading menu entries: ${error}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
||||||
selectTab(url: string) {
|
|
||||||
if (!this.menu) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (url === '/') {
|
|
||||||
this.selectedTab = (this.menu[0] as any)?.title ?? '';
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const candidate = this.menu.slice(0, 5).find(category => url.includes(category.route));
|
|
||||||
this.selectedTab = candidate?.title ?? '';
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,19 +33,19 @@
|
|||||||
</ion-tab-bar>
|
</ion-tab-bar>
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<ion-tab-bar slot="bottom" [selectedTab]="selectedTab">
|
<ion-tab-bar slot="bottom" [selectedTab]="selectedTab$ | async">
|
||||||
<ion-menu-toggle>
|
<ion-menu-toggle>
|
||||||
<ion-tab-button class="menu-button">
|
<ion-tab-button class="menu-button">
|
||||||
<ion-icon name="menu"></ion-icon>
|
<ion-icon name="menu"></ion-icon>
|
||||||
</ion-tab-button>
|
</ion-tab-button>
|
||||||
</ion-menu-toggle>
|
</ion-menu-toggle>
|
||||||
<ion-tab-button
|
<ion-tab-button
|
||||||
*ngFor="let category of menu; first as isFirst"
|
*ngFor="let category of menus; first as isFirst"
|
||||||
[rootLink]="category.route"
|
[rootLink]="category.route"
|
||||||
[redirectedFrom]="category.route"
|
[redirectedFrom]="category.route"
|
||||||
[tab]="category.title"
|
[tab]="category.title"
|
||||||
>
|
>
|
||||||
<ion-icon [name]="category.icon"></ion-icon>
|
<ion-icon [name]="category.icon"></ion-icon>
|
||||||
<ion-label>{{ category.translations[language].title | titlecase }}</ion-label>
|
<ion-label>{{ 'title' | translateSimple: category | titlecase }}</ion-label>
|
||||||
</ion-tab-button>
|
</ion-tab-button>
|
||||||
</ion-tab-bar>
|
</ion-tab-bar>
|
||||||
@@ -18,9 +18,7 @@ import {FormsModule} from '@angular/forms';
|
|||||||
import {RouterModule, Routes} from '@angular/router';
|
import {RouterModule, Routes} from '@angular/router';
|
||||||
import {IonicModule} from '@ionic/angular';
|
import {IonicModule} from '@ionic/angular';
|
||||||
import {TranslateModule} from '@ngx-translate/core';
|
import {TranslateModule} from '@ngx-translate/core';
|
||||||
|
|
||||||
import {ThingTranslateModule} from '../../translation/thing-translate.module';
|
import {ThingTranslateModule} from '../../translation/thing-translate.module';
|
||||||
import {ConfigProvider} from '../config/config.provider';
|
|
||||||
import {SettingsItemComponent} from './item/settings-item.component';
|
import {SettingsItemComponent} from './item/settings-item.component';
|
||||||
import {SettingsPageComponent} from './page/settings-page.component';
|
import {SettingsPageComponent} from './page/settings-page.component';
|
||||||
import {SettingTranslatePipe} from './setting-translate.pipe';
|
import {SettingTranslatePipe} from './setting-translate.pipe';
|
||||||
@@ -60,13 +58,6 @@ const settingsRoutes: Routes = [{path: 'settings', component: SettingsPageCompon
|
|||||||
RouterModule.forChild(settingsRoutes),
|
RouterModule.forChild(settingsRoutes),
|
||||||
UtilModule,
|
UtilModule,
|
||||||
],
|
],
|
||||||
providers: [
|
providers: [ScheduleSyncService, SettingsProvider, CalendarService, ScheduleProvider, ThingTranslatePipe],
|
||||||
ScheduleSyncService,
|
|
||||||
ConfigProvider,
|
|
||||||
SettingsProvider,
|
|
||||||
CalendarService,
|
|
||||||
ScheduleProvider,
|
|
||||||
ThingTranslatePipe,
|
|
||||||
],
|
|
||||||
})
|
})
|
||||||
export class SettingsModule {}
|
export class SettingsModule {}
|
||||||
|
|||||||
@@ -16,8 +16,9 @@ import {Injectable} from '@angular/core';
|
|||||||
import {SCSetting, SCSettingValue, SCSettingValues} from '@openstapps/core';
|
import {SCSetting, SCSettingValue, SCSettingValues} from '@openstapps/core';
|
||||||
import deepMerge from 'deepmerge';
|
import deepMerge from 'deepmerge';
|
||||||
import {Subject} from 'rxjs';
|
import {Subject} from 'rxjs';
|
||||||
import {ConfigProvider} from '../config/config.provider';
|
|
||||||
import {StorageProvider} from '../storage/storage.provider';
|
import {StorageProvider} from '../storage/storage.provider';
|
||||||
|
import {ConfigProvider} from '../config/config.provider';
|
||||||
|
import {BeforeAppInit} from '../../before-app-init';
|
||||||
|
|
||||||
export const STORAGE_KEY_SETTINGS = 'settings';
|
export const STORAGE_KEY_SETTINGS = 'settings';
|
||||||
export const STORAGE_KEY_SETTINGS_SEPARATOR = '.';
|
export const STORAGE_KEY_SETTINGS_SEPARATOR = '.';
|
||||||
@@ -89,19 +90,19 @@ export interface SettingsAction {
|
|||||||
/**
|
/**
|
||||||
* Provider for app settings
|
* Provider for app settings
|
||||||
*/
|
*/
|
||||||
@Injectable()
|
@Injectable({
|
||||||
export class SettingsProvider {
|
providedIn: 'root',
|
||||||
|
})
|
||||||
|
export class SettingsProvider implements BeforeAppInit {
|
||||||
/**
|
/**
|
||||||
* Source of settings actions
|
* Source of settings actions
|
||||||
*/
|
*/
|
||||||
private settingsActionSource = new Subject<SettingsAction>();
|
private settingsActionSource = new Subject<SettingsAction>();
|
||||||
|
|
||||||
private needsInit = true;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Order of the setting categories
|
* Order of the setting categories
|
||||||
*/
|
*/
|
||||||
categoriesOrder: string[];
|
categoriesOrder: string[] = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Settings actions observable
|
* Settings actions observable
|
||||||
@@ -111,7 +112,45 @@ export class SettingsProvider {
|
|||||||
/**
|
/**
|
||||||
* Cache for the imported settings
|
* Cache for the imported settings
|
||||||
*/
|
*/
|
||||||
settingsCache: SettingsCache;
|
settingsCache: SettingsCache = {};
|
||||||
|
|
||||||
|
constructor(private storageProvider: StorageProvider, private config: ConfigProvider) {}
|
||||||
|
|
||||||
|
async beforeAppInit() {
|
||||||
|
try {
|
||||||
|
for (const setting of this.config.app.settings) {
|
||||||
|
this.addSetting(setting);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const category of Object.keys(this.settingsCache)) {
|
||||||
|
if (!this.categoriesOrder.includes(category)) {
|
||||||
|
this.categoriesOrder.push(category);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
this.settingsCache = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (await this.storageProvider.has(STORAGE_KEY_SETTING_VALUES)) {
|
||||||
|
// get setting values from StorageProvider into settingsCache
|
||||||
|
const valuesContainer: SettingValuesContainer = await this.storageProvider.get<SettingValuesContainer>(
|
||||||
|
STORAGE_KEY_SETTING_VALUES,
|
||||||
|
);
|
||||||
|
// iterate through keys of categories
|
||||||
|
for (const categoryKey of Object.keys(this.settingsCache)) {
|
||||||
|
// iterate through setting keys of category
|
||||||
|
for (const settingKey of Object.keys(this.settingsCache[categoryKey].settings)) {
|
||||||
|
// if saved setting value exists set it, otherwise set to default value
|
||||||
|
this.settingsCache[categoryKey].settings[settingKey].value =
|
||||||
|
valuesContainer[categoryKey] !== undefined &&
|
||||||
|
valuesContainer[categoryKey][settingKey] !== undefined
|
||||||
|
? valuesContainer[categoryKey][settingKey]
|
||||||
|
: this.settingsCache[categoryKey].settings[settingKey].defaultValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
await this.saveSettingValues();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return true if all given values are valid to possible values in given settingInput
|
* Return true if all given values are valid to possible values in given settingInput
|
||||||
@@ -192,21 +231,11 @@ export class SettingsProvider {
|
|||||||
return isValueValid;
|
return isValueValid;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param storage TODO
|
|
||||||
* @param configProvider TODO
|
|
||||||
*/
|
|
||||||
constructor(private readonly storage: StorageProvider, private readonly configProvider: ConfigProvider) {
|
|
||||||
this.categoriesOrder = [];
|
|
||||||
this.settingsCache = {};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add an Setting to the Cache if not exist and set undefined value to defaultValue
|
* Add an Setting to the Cache if not exist and set undefined value to defaultValue
|
||||||
* @param setting Setting with categories, defaultValue, name, input type and valid values
|
* @param setting Setting with categories, defaultValue, name, input type and valid values
|
||||||
*/
|
*/
|
||||||
private addSetting(setting: SCSetting): void {
|
addSetting(setting: SCSetting): void {
|
||||||
if (!this.categoryExists(setting.categories[0])) {
|
if (!this.categoryExists(setting.categories[0])) {
|
||||||
this.provideCategory(setting.categories[0]);
|
this.provideCategory(setting.categories[0]);
|
||||||
}
|
}
|
||||||
@@ -265,9 +294,7 @@ export class SettingsProvider {
|
|||||||
/**
|
/**
|
||||||
* Returns copy of cached settings
|
* Returns copy of cached settings
|
||||||
*/
|
*/
|
||||||
public async getCache(): Promise<SettingsCache> {
|
public getCache(): SettingsCache {
|
||||||
await this.init();
|
|
||||||
|
|
||||||
return JSON.parse(JSON.stringify(this.settingsCache));
|
return JSON.parse(JSON.stringify(this.settingsCache));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -284,8 +311,7 @@ export class SettingsProvider {
|
|||||||
* @param name the name of requested setting
|
* @param name the name of requested setting
|
||||||
* @throws Exception if setting is not provided
|
* @throws Exception if setting is not provided
|
||||||
*/
|
*/
|
||||||
public async getSetting(category: string, name: string): Promise<SCSetting> {
|
public getSetting(category: string, name: string): SCSetting {
|
||||||
await this.init();
|
|
||||||
if (this.settingExists(category, name)) {
|
if (this.settingExists(category, name)) {
|
||||||
// return a copy of the settings
|
// return a copy of the settings
|
||||||
return JSON.parse(JSON.stringify(this.settingsCache[category].settings[name]));
|
return JSON.parse(JSON.stringify(this.settingsCache[category].settings[name]));
|
||||||
@@ -299,8 +325,7 @@ export class SettingsProvider {
|
|||||||
* @param name the name of requested setting
|
* @param name the name of requested setting
|
||||||
* @throws Exception if setting is not provided
|
* @throws Exception if setting is not provided
|
||||||
*/
|
*/
|
||||||
public async getValue(category: string, name: string): Promise<SCSettingValue | SCSettingValues> {
|
public getValue(category: string, name: string): SCSettingValue | SCSettingValues {
|
||||||
await this.init();
|
|
||||||
if (this.settingExists(category, name)) {
|
if (this.settingExists(category, name)) {
|
||||||
// return a copy of the settings value
|
// return a copy of the settings value
|
||||||
return JSON.parse(JSON.stringify(this.settingsCache[category].settings[name].value));
|
return JSON.parse(JSON.stringify(this.settingsCache[category].settings[name].value));
|
||||||
@@ -308,47 +333,6 @@ export class SettingsProvider {
|
|||||||
throw new Error(`Setting "${name}" not provided`);
|
throw new Error(`Setting "${name}" not provided`);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Initializes settings from config and stored values if exist
|
|
||||||
*/
|
|
||||||
public async init(): Promise<void> {
|
|
||||||
if (!this.needsInit) return;
|
|
||||||
this.needsInit = false;
|
|
||||||
|
|
||||||
try {
|
|
||||||
const settings: SCSetting[] = this.configProvider.getValue('settings') as SCSetting[];
|
|
||||||
for (const setting of settings) this.addSetting(setting);
|
|
||||||
|
|
||||||
for (const category of Object.keys(this.settingsCache)) {
|
|
||||||
if (!this.categoriesOrder.includes(category)) {
|
|
||||||
this.categoriesOrder.push(category);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
this.settingsCache = {};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (await this.storage.has(STORAGE_KEY_SETTING_VALUES)) {
|
|
||||||
// get setting values from StorageProvider into settingsCache
|
|
||||||
const valuesContainer: SettingValuesContainer = await this.storage.get<SettingValuesContainer>(
|
|
||||||
STORAGE_KEY_SETTING_VALUES,
|
|
||||||
);
|
|
||||||
// iterate through keys of categories
|
|
||||||
for (const categoryKey of Object.keys(this.settingsCache)) {
|
|
||||||
// iterate through setting keys of category
|
|
||||||
for (const settingKey of Object.keys(this.settingsCache[categoryKey].settings)) {
|
|
||||||
// if saved setting value exists set it, otherwise set to default value
|
|
||||||
this.settingsCache[categoryKey].settings[settingKey].value =
|
|
||||||
valuesContainer[categoryKey] !== undefined &&
|
|
||||||
valuesContainer[categoryKey][settingKey] !== undefined
|
|
||||||
? valuesContainer[categoryKey][settingKey]
|
|
||||||
: this.settingsCache[categoryKey].settings[settingKey].defaultValue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
await this.saveSettingValues();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds given setting and its category if not exist
|
* Adds given setting and its category if not exist
|
||||||
* @param setting the setting to add
|
* @param setting the setting to add
|
||||||
@@ -358,12 +342,10 @@ export class SettingsProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Deletes saved values and reinitialising the settings
|
* Delete saved values and reinitialize the settings
|
||||||
*/
|
*/
|
||||||
public async reset(): Promise<void> {
|
public async reset(): Promise<void> {
|
||||||
await this.storage.put(STORAGE_KEY_SETTING_VALUES, {});
|
await this.storageProvider.put(STORAGE_KEY_SETTING_VALUES, {});
|
||||||
this.needsInit = true;
|
|
||||||
await this.init();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -383,15 +365,14 @@ export class SettingsProvider {
|
|||||||
* Saves cached settings in app storage
|
* Saves cached settings in app storage
|
||||||
*/
|
*/
|
||||||
public async saveSettingValues(): Promise<void> {
|
public async saveSettingValues(): Promise<void> {
|
||||||
if (await this.storage.has(STORAGE_KEY_SETTING_VALUES)) {
|
if (await this.storageProvider.has(STORAGE_KEY_SETTING_VALUES)) {
|
||||||
const savedSettingsValues: SettingValuesContainer = await this.storage.get<SettingValuesContainer>(
|
const savedSettingsValues: SettingValuesContainer =
|
||||||
STORAGE_KEY_SETTING_VALUES,
|
await this.storageProvider.get<SettingValuesContainer>(STORAGE_KEY_SETTING_VALUES);
|
||||||
);
|
|
||||||
const cacheSettingsValues = this.getSettingValuesFromCache();
|
const cacheSettingsValues = this.getSettingValuesFromCache();
|
||||||
const mergedSettingValues = deepMerge(savedSettingsValues, cacheSettingsValues);
|
const mergedSettingValues = deepMerge(savedSettingsValues, cacheSettingsValues);
|
||||||
await this.storage.put<SettingValuesContainer>(STORAGE_KEY_SETTING_VALUES, mergedSettingValues);
|
await this.storageProvider.put<SettingValuesContainer>(STORAGE_KEY_SETTING_VALUES, mergedSettingValues);
|
||||||
} else {
|
} else {
|
||||||
await this.storage.put<SettingValuesContainer>(
|
await this.storageProvider.put<SettingValuesContainer>(
|
||||||
STORAGE_KEY_SETTING_VALUES,
|
STORAGE_KEY_SETTING_VALUES,
|
||||||
this.getSettingValuesFromCache(),
|
this.getSettingValuesFromCache(),
|
||||||
);
|
);
|
||||||
@@ -418,7 +399,6 @@ export class SettingsProvider {
|
|||||||
name: string,
|
name: string,
|
||||||
value: SCSettingValue | SCSettingValues,
|
value: SCSettingValue | SCSettingValues,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
await this.init();
|
|
||||||
if (this.settingExists(category, name)) {
|
if (this.settingExists(category, name)) {
|
||||||
const setting: SCSetting = this.settingsCache[category].settings[name];
|
const setting: SCSetting = this.settingsCache[category].settings[name];
|
||||||
const isValueValid = SettingsProvider.validateValue(setting, value);
|
const isValueValid = SettingsProvider.validateValue(setting, value);
|
||||||
|
|||||||
@@ -14,17 +14,22 @@
|
|||||||
*/
|
*/
|
||||||
import {Injectable} from '@angular/core';
|
import {Injectable} from '@angular/core';
|
||||||
import {Storage} from '@ionic/storage-angular';
|
import {Storage} from '@ionic/storage-angular';
|
||||||
|
import {BeforeAppInit} from '../../before-app-init';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provides interaction with the (ionic) storage on the device (in the browser)
|
* Provides interaction with the (ionic) storage on the device (in the browser)
|
||||||
*/
|
*/
|
||||||
@Injectable()
|
@Injectable({providedIn: 'root'})
|
||||||
export class StorageProvider {
|
export class StorageProvider implements BeforeAppInit {
|
||||||
/**
|
/**
|
||||||
* @param storage TODO
|
* @param storage TODO
|
||||||
*/
|
*/
|
||||||
constructor(private readonly storage: Storage) {}
|
constructor(private readonly storage: Storage) {}
|
||||||
|
|
||||||
|
async beforeAppInit() {
|
||||||
|
await this.storage.create();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Deletes storage entries using keys used to save them
|
* Deletes storage entries using keys used to save them
|
||||||
* @param keys Unique identifiers of the resources for deletion
|
* @param keys Unique identifiers of the resources for deletion
|
||||||
@@ -95,13 +100,6 @@ export class StorageProvider {
|
|||||||
return (await this.storage.keys()).includes(key);
|
return (await this.storage.keys()).includes(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Initializes the storage (waits until it's ready)
|
|
||||||
*/
|
|
||||||
async init(): Promise<void> {
|
|
||||||
await this.storage.create();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provides information if storage is empty or not
|
* Provides information if storage is empty or not
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ import {Injectable, OnDestroy, Pipe, PipeTransform} from '@angular/core';
|
|||||||
import {LangChangeEvent, TranslateService} from '@ngx-translate/core';
|
import {LangChangeEvent, TranslateService} from '@ngx-translate/core';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import {Subscription} from 'rxjs';
|
import {Subscription} from 'rxjs';
|
||||||
import {logger} from '../_helpers/ts-logger';
|
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
@Pipe({
|
@Pipe({
|
||||||
@@ -163,7 +162,7 @@ export class DurationLocalizedPipe implements PipeTransform, OnDestroy {
|
|||||||
|
|
||||||
updateValue(value: string | unknown, isFrequency = false): void {
|
updateValue(value: string | unknown, isFrequency = false): void {
|
||||||
if (typeof value !== 'string') {
|
if (typeof value !== 'string') {
|
||||||
logger.warn(`durationLocalized pipe unable to parse input: ${value}`);
|
console.warn('durationLocalized pipe unable to parse input', value);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -223,7 +222,7 @@ export class MetersLocalizedPipe implements PipeTransform, OnDestroy {
|
|||||||
|
|
||||||
updateValue(value: string | number | unknown) {
|
updateValue(value: string | number | unknown) {
|
||||||
if (typeof value !== 'string' && typeof value !== 'number') {
|
if (typeof value !== 'string' && typeof value !== 'number') {
|
||||||
logger.warn(`metersLocalized pipe unable to parse input: ${value}`);
|
console.warn('metersLocalized pipe unable to parse input:', value);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -321,7 +320,7 @@ export class NumberLocalizedPipe implements PipeTransform, OnDestroy {
|
|||||||
|
|
||||||
updateValue(value: string | number | unknown, formatOptions?: string): void {
|
updateValue(value: string | number | unknown, formatOptions?: string): void {
|
||||||
if (typeof value !== 'string' && typeof value !== 'number') {
|
if (typeof value !== 'string' && typeof value !== 'number') {
|
||||||
logger.warn(`numberLocalized pipe unable to parse input: ${value}`);
|
console.warn('numberLocalized pipe unable to parse input:', value);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -387,7 +386,7 @@ export class DateLocalizedFormatPipe implements PipeTransform, OnDestroy {
|
|||||||
|
|
||||||
updateValue(value: string | Date | unknown, formatOptions?: string): void {
|
updateValue(value: string | Date | unknown, formatOptions?: string): void {
|
||||||
if (typeof value !== 'string' && Object.prototype.toString.call(value) !== '[object Date]') {
|
if (typeof value !== 'string' && Object.prototype.toString.call(value) !== '[object Date]') {
|
||||||
logger.warn(`dateFormat pipe unable to parse input: ${value}`);
|
console.warn('dateFormat pipe unable to parse input:', value);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,38 @@
|
|||||||
|
import {SCLanguageCode, SCSettingValue} from '@openstapps/core';
|
||||||
|
import moment from 'moment/moment';
|
||||||
|
import {getDateFnsLocale} from './dfns-locale';
|
||||||
|
import {setDefaultOptions} from 'date-fns';
|
||||||
|
import {SettingsProvider} from '../modules/settings/settings.provider';
|
||||||
|
import {TranslateService} from '@ngx-translate/core';
|
||||||
|
import {DateFnsConfigurationService} from 'ngx-date-fns';
|
||||||
|
import {ConfigProvider} from '../modules/config/config.provider';
|
||||||
|
import {BeforeAppInit} from '../before-app-init';
|
||||||
|
import {inject, Injectable} from '@angular/core';
|
||||||
|
|
||||||
|
@Injectable({providedIn: 'root'})
|
||||||
|
export class TranslateServiceWrapper extends TranslateService implements BeforeAppInit {
|
||||||
|
private config = inject(ConfigProvider);
|
||||||
|
|
||||||
|
private settingsProvider = inject(SettingsProvider);
|
||||||
|
|
||||||
|
private dateFnsConfigurationService = inject(DateFnsConfigurationService);
|
||||||
|
|
||||||
|
async beforeAppInit() {
|
||||||
|
if (this.config.isFirstSession) {
|
||||||
|
// set language from browser
|
||||||
|
await this.settingsProvider.setSettingValue(
|
||||||
|
'profile',
|
||||||
|
'language',
|
||||||
|
this.getBrowserLang() as SCSettingValue,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const languageCode = this.settingsProvider.getValue('profile', 'language') as string;
|
||||||
|
// this language will be used as a fallback when a translation isn't found in the current language
|
||||||
|
this.setDefaultLang('en');
|
||||||
|
this.use(languageCode);
|
||||||
|
moment.locale(languageCode);
|
||||||
|
const dateFnsLocale = await getDateFnsLocale(languageCode as SCLanguageCode);
|
||||||
|
setDefaultOptions({locale: dateFnsLocale});
|
||||||
|
this.dateFnsConfigurationService.setLocale(dateFnsLocale);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -21,6 +21,7 @@ import {
|
|||||||
race,
|
race,
|
||||||
RetryConfig,
|
RetryConfig,
|
||||||
share,
|
share,
|
||||||
|
skip,
|
||||||
Subject,
|
Subject,
|
||||||
takeUntil,
|
takeUntil,
|
||||||
} from 'rxjs';
|
} from 'rxjs';
|
||||||
@@ -61,7 +62,7 @@ export class InternetConnectionService {
|
|||||||
private doRetry(error: unknown, retryCount: number): ObservableInput<unknown> {
|
private doRetry(error: unknown, retryCount: number): ObservableInput<unknown> {
|
||||||
return race(
|
return race(
|
||||||
this.offline$.pipe(
|
this.offline$.pipe(
|
||||||
tap(it => console.log(it)),
|
skip(1),
|
||||||
filter(it => !it),
|
filter(it => !it),
|
||||||
take(1),
|
take(1),
|
||||||
delay(Math.min(retryCount ** 4 + 100, 10_000)),
|
delay(Math.min(retryCount ** 4 + 100, 10_000)),
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ export const environment = {
|
|||||||
backend_url: 'https://mobile.server.uni-frankfurt.de',
|
backend_url: 'https://mobile.server.uni-frankfurt.de',
|
||||||
app_host: 'mobile.app.uni-frankfurt.de',
|
app_host: 'mobile.app.uni-frankfurt.de',
|
||||||
custom_url_scheme: 'de.anyschool.app',
|
custom_url_scheme: 'de.anyschool.app',
|
||||||
backend_version: '4.0.0',
|
backend_version: '0.0.0',
|
||||||
production: false,
|
production: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user