mirror of
https://gitlab.com/openstapps/openstapps.git
synced 2026-10-03 16:22:05 +00:00
feat: show menu for multiple days for canteens and cafes
Closes #19, #79
This commit is contained in:
committed by
Jovan Krunić
parent
66b8720da0
commit
3c079cd189
Generated
+603
-397
File diff suppressed because it is too large
Load Diff
+1
-1
@@ -50,7 +50,7 @@
|
|||||||
"@ionic/storage": "2.2.0",
|
"@ionic/storage": "2.2.0",
|
||||||
"@ngx-translate/core": "11.0.1",
|
"@ngx-translate/core": "11.0.1",
|
||||||
"@ngx-translate/http-loader": "4.0.0",
|
"@ngx-translate/http-loader": "4.0.0",
|
||||||
"@openstapps/api": "0.22.0",
|
"@openstapps/api": "0.25.0",
|
||||||
"@openstapps/configuration": "0.25.0",
|
"@openstapps/configuration": "0.25.0",
|
||||||
"@openstapps/core": "0.36.0",
|
"@openstapps/core": "0.36.0",
|
||||||
"cordova-android": "8.0.0",
|
"cordova-android": "8.0.0",
|
||||||
|
|||||||
@@ -13,6 +13,9 @@
|
|||||||
* this program. If not, see <https://www.gnu.org/licenses/>.
|
* this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
/* tslint:disable */
|
/* tslint:disable */
|
||||||
|
|
||||||
|
import moment from 'moment'
|
||||||
|
|
||||||
export const sampleResources = [{
|
export const sampleResources = [{
|
||||||
'errorNames': [],
|
'errorNames': [],
|
||||||
"instance": {
|
"instance": {
|
||||||
@@ -981,8 +984,8 @@ export const sampleResources = [{
|
|||||||
'offers': [
|
'offers': [
|
||||||
{
|
{
|
||||||
'availability': 'in stock',
|
'availability': 'in stock',
|
||||||
'availabilityStarts': '2017-01-30T00:00:00.000Z',
|
'availabilityStarts': moment().startOf('day').add(2,'days').toISOString(),
|
||||||
'availabilityEnds': '2017-01-30T23:59:59.999Z',
|
'availabilityEnds': moment().endOf('day').add(2,'days').toISOString(),
|
||||||
'prices': {
|
'prices': {
|
||||||
'default': 6.5,
|
'default': 6.5,
|
||||||
'student': 5,
|
'student': 5,
|
||||||
@@ -1101,8 +1104,8 @@ export const sampleResources = [{
|
|||||||
'offers': [
|
'offers': [
|
||||||
{
|
{
|
||||||
'availability': 'in stock',
|
'availability': 'in stock',
|
||||||
'availabilityStarts': '2017-01-30T00:00:00.000Z',
|
'availabilityStarts': moment().startOf('day').toISOString(),
|
||||||
'availabilityEnds': '2017-01-30T23:59:59.999Z',
|
'availabilityEnds': moment().endOf('day').add(2,'days').toISOString(),
|
||||||
'prices': {
|
'prices': {
|
||||||
'default': 4.85,
|
'default': 4.85,
|
||||||
'student': 2.85,
|
'student': 2.85,
|
||||||
@@ -1117,7 +1120,7 @@ export const sampleResources = [{
|
|||||||
'type': 'remote'
|
'type': 'remote'
|
||||||
},
|
},
|
||||||
'type': 'organization',
|
'type': 'organization',
|
||||||
'uid': '3b9b3df6-3a7a-58cc-922f-c7335c002634'
|
'uid': 'b7206fb5-bd77-5572-928f-16aa70910f64'
|
||||||
},
|
},
|
||||||
'inPlace': {
|
'inPlace': {
|
||||||
'geo': {
|
'geo': {
|
||||||
@@ -1138,7 +1141,7 @@ export const sampleResources = [{
|
|||||||
'alternateNames': [
|
'alternateNames': [
|
||||||
'MensaHardenberg'
|
'MensaHardenberg'
|
||||||
],
|
],
|
||||||
'uid': '72fbc8a3-ebd1-58f9-9526-ad65cba2e402',
|
'uid': 'b7206fb5-bd77-5572-928f-16aa70910f64',
|
||||||
'address': {
|
'address': {
|
||||||
'addressCountry': 'Germany',
|
'addressCountry': 'Germany',
|
||||||
'addressLocality': 'Berlin',
|
'addressLocality': 'Berlin',
|
||||||
@@ -1191,8 +1194,8 @@ export const sampleResources = [{
|
|||||||
'uid': '3b9b3df6-3a7a-58cc-922f-c7335c002634'
|
'uid': '3b9b3df6-3a7a-58cc-922f-c7335c002634'
|
||||||
},
|
},
|
||||||
'availability': 'in stock',
|
'availability': 'in stock',
|
||||||
'availabilityStarts': '2017-01-30T00:00:00.000Z',
|
'availabilityStarts': moment().startOf('day').add(2,'days').toISOString(),
|
||||||
'availabilityEnds': '2017-01-30T23:59:59.999Z',
|
'availabilityEnds': moment().endOf('day').add(2,'days').toISOString(),
|
||||||
'inPlace': {
|
'inPlace': {
|
||||||
'geo': {
|
'geo': {
|
||||||
'point': {
|
'point': {
|
||||||
@@ -1264,8 +1267,8 @@ export const sampleResources = [{
|
|||||||
],
|
],
|
||||||
'offers': [
|
'offers': [
|
||||||
{
|
{
|
||||||
'availabilityEnds': '2017-03-27T23:59:59.000Z',
|
'availabilityEnds': moment().endOf('day').toISOString(),
|
||||||
'availabilityStarts': '2017-03-27T00:00:00.000Z',
|
'availabilityStarts': moment().startOf('day').toISOString(),
|
||||||
'availability': 'in stock',
|
'availability': 'in stock',
|
||||||
'inPlace': {
|
'inPlace': {
|
||||||
'type': 'room',
|
'type': 'room',
|
||||||
@@ -1273,7 +1276,7 @@ export const sampleResources = [{
|
|||||||
'categories': [
|
'categories': [
|
||||||
'cafe'
|
'cafe'
|
||||||
],
|
],
|
||||||
'uid': 'e5492c9c-064e-547c-8633-c8fc8955cfcf',
|
'uid': 'b7206fb5-bd77-5572-928f-16aa70910f64',
|
||||||
'alternateNames': [
|
'alternateNames': [
|
||||||
'Cafeteria LEVEL'
|
'Cafeteria LEVEL'
|
||||||
],
|
],
|
||||||
@@ -1306,7 +1309,7 @@ export const sampleResources = [{
|
|||||||
'type': 'remote'
|
'type': 'remote'
|
||||||
},
|
},
|
||||||
'type': 'organization',
|
'type': 'organization',
|
||||||
'uid': '3b9b3df6-3a7a-58cc-922f-c7335c002634'
|
'uid': 'b7206fb5-bd77-5572-928f-16aa70910f64'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -14,10 +14,13 @@
|
|||||||
*/
|
*/
|
||||||
import {HttpClient} from '@angular/common/http';
|
import {HttpClient} from '@angular/common/http';
|
||||||
import {Injectable} from '@angular/core';
|
import {Injectable} from '@angular/core';
|
||||||
import {SCAcademicEvent, SCArticle, SCBook, SCBuilding, SCCatalog,
|
import {
|
||||||
SCDateSeries, SCDish, SCFavorite, SCMessage, SCPerson, SCRoom,
|
SCAcademicEvent, SCArticle, SCBook, SCBuilding, SCCatalog,
|
||||||
SCThing, SCThingOriginType, SCThingType, SCToDo, SCToDoPriority} from '@openstapps/core';
|
SCDateSeries, SCDish, SCFavorite, SCMessage, SCPerson, SCRoom, SCSearchFilter,
|
||||||
|
SCThing, SCThingOriginType, SCThingType, SCToDo, SCToDoPriority,
|
||||||
|
} from '@openstapps/core';
|
||||||
import {Observable, of} from 'rxjs';
|
import {Observable, of} from 'rxjs';
|
||||||
|
import {checkFilter} from '../fakesearch/filters';
|
||||||
import {sampleResources} from './resources/test-resources';
|
import {sampleResources} from './resources/test-resources';
|
||||||
|
|
||||||
// tslint:disable:no-magic-numbers
|
// tslint:disable:no-magic-numbers
|
||||||
@@ -339,7 +342,7 @@ const sampleDateSeries: SCDateSeries[] = [
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
export const sampleThingsMap: {[key in SCThingType | string]: SCThing[]} = {
|
export const sampleThingsMap: { [key in SCThingType | string]: SCThing[] } = {
|
||||||
'academic event': sampleAcademicEvents,
|
'academic event': sampleAcademicEvents,
|
||||||
article: sampleArticles,
|
article: sampleArticles,
|
||||||
book: sampleBooks,
|
book: sampleBooks,
|
||||||
@@ -364,35 +367,6 @@ export const sampleThingsMap: {[key in SCThingType | string]: SCThing[]} = {
|
|||||||
tour: [],
|
tour: [],
|
||||||
video: [],
|
video: [],
|
||||||
};
|
};
|
||||||
// tslint:enable:no-magic-numbers
|
|
||||||
|
|
||||||
/**
|
|
||||||
* TODO
|
|
||||||
*
|
|
||||||
* provides all or certain sample things to be used in tests or backendless development
|
|
||||||
*/
|
|
||||||
export function getSampleThings(...uids: string[]): SCThing[] {
|
|
||||||
const sampleThings: SCThing[] = [];
|
|
||||||
if (typeof uids !== 'undefined' && uids.length > 0) {
|
|
||||||
uids.forEach((uid) => {
|
|
||||||
Object.keys(sampleThingsMap)
|
|
||||||
.forEach((key) => {
|
|
||||||
sampleThingsMap[key].forEach((thing) => {
|
|
||||||
if (thing.uid === uid) {
|
|
||||||
sampleThings.push(thing);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
Object.keys(sampleThingsMap)
|
|
||||||
.forEach((key) => {
|
|
||||||
sampleThings.push(...sampleThingsMap[key]);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return sampleThings;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TODO
|
* TODO
|
||||||
@@ -407,17 +381,13 @@ export class SampleThings {
|
|||||||
constructor(http: HttpClient) {
|
constructor(http: HttpClient) {
|
||||||
this.http = http;
|
this.http = http;
|
||||||
}
|
}
|
||||||
// getSampleThings(): Observable<any> {
|
|
||||||
// return this.http.get('http://localhost:8100/assets/json/joined.json');
|
|
||||||
// // return of(sampleDishes[0]);
|
|
||||||
// }
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TODO
|
* TODO
|
||||||
*/
|
*/
|
||||||
// tslint:disable-next-line:prefer-function-over-method
|
// tslint:disable-next-line:prefer-function-over-method no-any
|
||||||
getSampleThing(uid: string): Observable<any[]> {
|
getSampleThing(uid: string): Observable<any[]> {
|
||||||
// const jsonContent: any[] = await this.http.get('http://localhost:8100/assets/json/joined.json').toPromise();
|
// tslint:disable-next-line:no-any
|
||||||
const sampleThings: any[] = [];
|
const sampleThings: any[] = [];
|
||||||
for (const resource of sampleResources) {
|
for (const resource of sampleResources) {
|
||||||
if (resource.instance.uid as SCThingType === uid) {
|
if (resource.instance.uid as SCThingType === uid) {
|
||||||
@@ -433,16 +403,18 @@ export class SampleThings {
|
|||||||
/**
|
/**
|
||||||
* TODO
|
* TODO
|
||||||
*/
|
*/
|
||||||
// tslint:disable-next-line:prefer-function-over-method
|
// tslint:disable-next-line:prefer-function-over-method no-any
|
||||||
getSampleThings(): Observable<any[]> {
|
getSampleThings(filter?: SCSearchFilter): Observable<any[]> {
|
||||||
// const jsonContent: any[] = await this.http.get('http://localhost:8100/assets/json/joined.json').toPromise();
|
// tslint:disable-next-line:no-any
|
||||||
const sampleThings: any[] = [];
|
const sampleThings: any[] = [];
|
||||||
sampleResources.forEach((resource) => {
|
for (const resource of sampleResources) {
|
||||||
// tslint:disable-next-line:max-line-length
|
// tslint:disable-next-line:max-line-length
|
||||||
// if ([SCThingType.Video].includes(resource.instance.type as SCThingType)) {
|
// if ([SCThingType.Video].includes(resource.instance.type as SCThingType)) {
|
||||||
|
if (typeof filter === 'undefined' || checkFilter(resource.instance as SCThing, filter)) {
|
||||||
sampleThings.push(resource.instance);
|
sampleThings.push(resource.instance);
|
||||||
|
}
|
||||||
// }
|
// }
|
||||||
});
|
}
|
||||||
|
|
||||||
return of(sampleThings);
|
return of(sampleThings);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -382,13 +382,13 @@ export class FakeBackendInterceptor implements HttpInterceptor {
|
|||||||
if (request.body.filter.arguments.field === 'uid') {
|
if (request.body.filter.arguments.field === 'uid') {
|
||||||
return this.sampleFetcher.getSampleThing(request.body.filter.arguments.value)
|
return this.sampleFetcher.getSampleThing(request.body.filter.arguments.value)
|
||||||
// tslint:disable-next-line:no-any
|
// tslint:disable-next-line:no-any
|
||||||
.pipe(map((sampleData: any) => {
|
.pipe(map((sampleData: any) => {
|
||||||
return new HttpResponse({status: 200, body: {data: sampleData}});
|
return new HttpResponse({status: 200, body: {data: sampleData}});
|
||||||
}), delay(this.RESPONSE_DELAY)); // add delay for skeleton screens to be seen (see !16)
|
}), delay(this.RESPONSE_DELAY)); // add delay for skeleton screens to be seen (see !16)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.sampleFetcher.getSampleThings()
|
return this.sampleFetcher.getSampleThings(request.body.filter)
|
||||||
// tslint:disable-next-line:no-any
|
// tslint:disable-next-line:no-any
|
||||||
.pipe(map((sampleData: any) => {
|
.pipe(map((sampleData: any) => {
|
||||||
return new HttpResponse({status: 200, body: {data: sampleData, facets: facetsMock}});
|
return new HttpResponse({status: 200, body: {data: sampleData, facets: facetsMock}});
|
||||||
|
|||||||
@@ -0,0 +1,170 @@
|
|||||||
|
/*
|
||||||
|
* 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 {SCSearchBooleanFilter, SCSearchFilter, SCSearchValueFilter, SCThing} from '@openstapps/core';
|
||||||
|
import {logger} from '../ts-logger';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if any filter applies to an SCThing
|
||||||
|
*/
|
||||||
|
export function checkFilter(thing: SCThing, filter: SCSearchFilter): boolean {
|
||||||
|
switch (filter.type) {
|
||||||
|
case 'availability': /*TODO*/
|
||||||
|
break;
|
||||||
|
case 'boolean':
|
||||||
|
return applyBooleanFilter(thing, filter);
|
||||||
|
case 'distance': /*TODO*/
|
||||||
|
break;
|
||||||
|
case 'value':
|
||||||
|
return applyValueFilter(thing, filter);
|
||||||
|
}
|
||||||
|
|
||||||
|
void logger.error(`Not implemented filter method "${filter.type}" in fake backend!`);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if a value filter applies to an SCThing
|
||||||
|
*/
|
||||||
|
function applyValueFilter(thing: SCThing, filter: SCSearchValueFilter): boolean {
|
||||||
|
const path = filter.arguments.field.split('.');
|
||||||
|
const thingFieldValue = traverseToFieldPath(thing, path, filter.arguments.value);
|
||||||
|
|
||||||
|
if (!(thingFieldValue.found)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return thingFieldValue.result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Object that can be accessed using foo[bar]
|
||||||
|
*/
|
||||||
|
interface IndexableObject {
|
||||||
|
// tslint:disable-next-line:no-any
|
||||||
|
[key: string]: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Result of a search for a field and comparison to a desired value
|
||||||
|
*/
|
||||||
|
type FieldSearchResult = {
|
||||||
|
/**
|
||||||
|
* Weather the field was found
|
||||||
|
*/
|
||||||
|
found: true;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The result of the comparison
|
||||||
|
*/
|
||||||
|
result: boolean;
|
||||||
|
} | {
|
||||||
|
/**
|
||||||
|
* Weather the field was found
|
||||||
|
*/
|
||||||
|
found: false;
|
||||||
|
};
|
||||||
|
|
||||||
|
// tslint:disable-next-line:completed-docs
|
||||||
|
function traverseToFieldPath(value: IndexableObject, path: string[], desiredFieldValue: unknown): FieldSearchResult {
|
||||||
|
if (path.length === 0) {
|
||||||
|
void logger.error(`Value filter provided with zero length path`);
|
||||||
|
|
||||||
|
return {found: false};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value.hasOwnProperty(path[0])) {
|
||||||
|
const nestedProperty = value[path[0]];
|
||||||
|
|
||||||
|
if (path.length === 1) {
|
||||||
|
return esStyleFieldHandler(nestedProperty, (nestedValue) => {
|
||||||
|
return {
|
||||||
|
found: true,
|
||||||
|
result: nestedValue === desiredFieldValue,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return esStyleFieldHandler(nestedProperty, (nestedValue) => {
|
||||||
|
if (typeof nestedValue === 'object') {
|
||||||
|
return traverseToFieldPath(
|
||||||
|
nestedValue as IndexableObject,
|
||||||
|
// tslint:disable-next-line:no-magic-numbers
|
||||||
|
path.slice(1),
|
||||||
|
desiredFieldValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {found: false};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return {found: false};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ES treats arrays like normal fields
|
||||||
|
*/
|
||||||
|
function esStyleFieldHandler<T>(field: T | T[],
|
||||||
|
handler: (value: T) => FieldSearchResult): FieldSearchResult {
|
||||||
|
if (Array.isArray(field)) {
|
||||||
|
for (const nestedField of field) {
|
||||||
|
const result = handler(nestedField);
|
||||||
|
|
||||||
|
if (result.found && result.result) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: found is not accurate
|
||||||
|
return {found: false};
|
||||||
|
}
|
||||||
|
|
||||||
|
return handler(field);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if a boolean filter applies to an SCThing
|
||||||
|
*/
|
||||||
|
function applyBooleanFilter(thing: SCThing, filter: SCSearchBooleanFilter): boolean {
|
||||||
|
let out = false;
|
||||||
|
|
||||||
|
switch (filter.arguments.operation) {
|
||||||
|
case 'and':
|
||||||
|
out = true;
|
||||||
|
for (const nesterFilter of filter.arguments.filters) {
|
||||||
|
out = out && checkFilter(thing, nesterFilter);
|
||||||
|
}
|
||||||
|
|
||||||
|
return out;
|
||||||
|
case 'or':
|
||||||
|
for (const nesterFilter of filter.arguments.filters) {
|
||||||
|
out = out || checkFilter(thing, nesterFilter);
|
||||||
|
}
|
||||||
|
|
||||||
|
return out;
|
||||||
|
case 'not':
|
||||||
|
if (filter.arguments.filters.length === 1) {
|
||||||
|
return !checkFilter(thing, filter.arguments.filters[0]);
|
||||||
|
}
|
||||||
|
void logger.error(`Too many filters for "not" boolean operation`);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void logger.error(`Not implemented boolean filter "${filter.arguments.operation}"`);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
/*
|
||||||
|
* 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,6 +21,7 @@ import {NGXLogger} from 'ngx-logger';
|
|||||||
import {ConfigProvider} from './modules/config/config.provider';
|
import {ConfigProvider} from './modules/config/config.provider';
|
||||||
import {SettingsProvider} from './modules/settings/settings.provider';
|
import {SettingsProvider} from './modules/settings/settings.provider';
|
||||||
|
|
||||||
|
import {initLogger} from './_helpers/ts-logger';
|
||||||
/**
|
/**
|
||||||
* TODO
|
* TODO
|
||||||
*/
|
*/
|
||||||
@@ -60,6 +61,7 @@ export class AppComponent {
|
|||||||
private readonly settingsProvider: SettingsProvider,
|
private readonly settingsProvider: SettingsProvider,
|
||||||
private readonly configProvider: ConfigProvider,
|
private readonly configProvider: ConfigProvider,
|
||||||
private readonly logger: NGXLogger) {
|
private readonly logger: NGXLogger) {
|
||||||
|
initLogger(logger);
|
||||||
this.initializeApp();
|
this.initializeApp();
|
||||||
|
|
||||||
// this language will be used as a fallback when a translation isn't found in the current language
|
// this language will be used as a fallback when a translation isn't found in the current language
|
||||||
|
|||||||
@@ -16,10 +16,12 @@ import {NgModule} from '@angular/core';
|
|||||||
import {RouterModule, Routes} from '@angular/router';
|
import {RouterModule, Routes} from '@angular/router';
|
||||||
import {DataDetailComponent} from './detail/data-detail.component';
|
import {DataDetailComponent} from './detail/data-detail.component';
|
||||||
import {DataListComponent} from './list/data-list.component';
|
import {DataListComponent} from './list/data-list.component';
|
||||||
|
import {FoodDataListComponent} from './list/food-data-list.component';
|
||||||
|
|
||||||
const dataRoutes: Routes = [
|
const dataRoutes: Routes = [
|
||||||
{path: 'search', component: DataListComponent},
|
{path: 'search', component: DataListComponent},
|
||||||
{path: 'data-detail/:uid', component: DataDetailComponent},
|
{path: 'data-detail/:uid', component: DataDetailComponent},
|
||||||
|
{path: 'canteen', component: FoodDataListComponent},
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -35,9 +35,11 @@ import {OriginDetailComponent} from './elements/origin-detail.component';
|
|||||||
import {OriginInListComponent} from './elements/origin-in-list.component';
|
import {OriginInListComponent} from './elements/origin-in-list.component';
|
||||||
import {SimpleCardComponent} from './elements/simple-card.component';
|
import {SimpleCardComponent} from './elements/simple-card.component';
|
||||||
import {SkeletonListItem} from './elements/skeleton-list-item.component';
|
import {SkeletonListItem} from './elements/skeleton-list-item.component';
|
||||||
|
import {SkeletonSegment} from './elements/skeleton-segment-button.component';
|
||||||
import {SkeletonSimpleCard} from './elements/skeleton-simple-card.component';
|
import {SkeletonSimpleCard} from './elements/skeleton-simple-card.component';
|
||||||
import {DataListItem} from './list/data-list-item.component';
|
import {DataListItem} from './list/data-list-item.component';
|
||||||
import {DataListComponent} from './list/data-list.component';
|
import {DataListComponent} from './list/data-list.component';
|
||||||
|
import {FoodDataListComponent} from './list/food-data-list.component';
|
||||||
import {StAppsWebHttpClient} from './stapps-web-http-client.provider';
|
import {StAppsWebHttpClient} from './stapps-web-http-client.provider';
|
||||||
import {ArticleDetailContentComponent} from './types/article/article-detail-content.component';
|
import {ArticleDetailContentComponent} from './types/article/article-detail-content.component';
|
||||||
import {ArticleListItem} from './types/article/article-list-item.component';
|
import {ArticleListItem} from './types/article/article-list-item.component';
|
||||||
@@ -59,6 +61,7 @@ import {PersonDetailContentComponent} from './types/person/person-detail-content
|
|||||||
import {PersonListItem} from './types/person/person-list-item.component';
|
import {PersonListItem} from './types/person/person-list-item.component';
|
||||||
import {PlaceDetailContentComponent} from './types/place/place-detail-content.component';
|
import {PlaceDetailContentComponent} from './types/place/place-detail-content.component';
|
||||||
import {PlaceListItem} from './types/place/place-list-item.component';
|
import {PlaceListItem} from './types/place/place-list-item.component';
|
||||||
|
import {PlaceMensaDetailComponent} from './types/place/special/mensa/place-mensa-detail.component';
|
||||||
import {SemesterDetailContentComponent} from './types/semester/semester-detail-content.component';
|
import {SemesterDetailContentComponent} from './types/semester/semester-detail-content.component';
|
||||||
import {SemesterListItem} from './types/semester/semester-list-item.component';
|
import {SemesterListItem} from './types/semester/semester-list-item.component';
|
||||||
import {VideoDetailContentComponent} from './types/video/video-detail-content.component';
|
import {VideoDetailContentComponent} from './types/video/video-detail-content.component';
|
||||||
@@ -80,6 +83,7 @@ import {VideoListItem} from './types/video/video-list-item.component';
|
|||||||
CatalogListItem,
|
CatalogListItem,
|
||||||
DataDetailComponent,
|
DataDetailComponent,
|
||||||
DataDetailContentComponent,
|
DataDetailContentComponent,
|
||||||
|
FoodDataListComponent,
|
||||||
DataListComponent,
|
DataListComponent,
|
||||||
DataListItem,
|
DataListItem,
|
||||||
DateSeriesDetailContentComponent,
|
DateSeriesDetailContentComponent,
|
||||||
@@ -101,9 +105,11 @@ import {VideoListItem} from './types/video/video-list-item.component';
|
|||||||
PersonListItem,
|
PersonListItem,
|
||||||
PlaceDetailContentComponent,
|
PlaceDetailContentComponent,
|
||||||
PlaceListItem,
|
PlaceListItem,
|
||||||
|
PlaceMensaDetailComponent,
|
||||||
SemesterDetailContentComponent,
|
SemesterDetailContentComponent,
|
||||||
SemesterListItem,
|
SemesterListItem,
|
||||||
SkeletonListItem,
|
SkeletonListItem,
|
||||||
|
SkeletonSegment,
|
||||||
VideoDetailContentComponent,
|
VideoDetailContentComponent,
|
||||||
VideoListItem,
|
VideoListItem,
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -16,9 +16,9 @@ import {Injectable} from '@angular/core';
|
|||||||
import {Client} from '@openstapps/api/lib/client';
|
import {Client} from '@openstapps/api/lib/client';
|
||||||
import {SCSearchQuery, SCSearchResponse, SCThingOriginType, SCThings, SCThingType} from '@openstapps/core';
|
import {SCSearchQuery, SCSearchResponse, SCThingOriginType, SCThings, SCThingType} from '@openstapps/core';
|
||||||
import {SCSaveableThing} from '@openstapps/core';
|
import {SCSaveableThing} from '@openstapps/core';
|
||||||
|
import {environment} from '../../../environments/environment';
|
||||||
import {StorageProvider} from '../storage/storage.provider';
|
import {StorageProvider} from '../storage/storage.provider';
|
||||||
import {StAppsWebHttpClient} from './stapps-web-http-client.provider';
|
import {StAppsWebHttpClient} from './stapps-web-http-client.provider';
|
||||||
import {environment} from './../../../environments/environment';
|
|
||||||
|
|
||||||
export enum DataScope {
|
export enum DataScope {
|
||||||
Local = 'local',
|
Local = 'local',
|
||||||
|
|||||||
@@ -27,4 +27,5 @@ export class DataDetailContentComponent {
|
|||||||
* TODO
|
* TODO
|
||||||
*/
|
*/
|
||||||
@Input() item: SCThings;
|
@Input() item: SCThings;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
/*
|
||||||
|
* 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 {Component} from '@angular/core';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODO
|
||||||
|
*/
|
||||||
|
@Component({
|
||||||
|
selector: 'stapps-skeleton-segment-button',
|
||||||
|
templateUrl: 'skeleton-segment-button.html',
|
||||||
|
})
|
||||||
|
export class SkeletonSegment {
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
<ion-segment-button>
|
||||||
|
<ion-skeleton-text animated style="width: 85%;"></ion-skeleton-text>
|
||||||
|
</ion-segment-button>
|
||||||
|
|
||||||
@@ -22,9 +22,9 @@ import {
|
|||||||
SCThing,
|
SCThing,
|
||||||
} from '@openstapps/core';
|
} from '@openstapps/core';
|
||||||
import {NGXLogger} from 'ngx-logger';
|
import {NGXLogger} from 'ngx-logger';
|
||||||
import {Subject} from 'rxjs';
|
import {Subject, Subscription} from 'rxjs';
|
||||||
import {debounceTime, distinctUntilChanged} from 'rxjs/operators';
|
import {debounceTime, distinctUntilChanged} from 'rxjs/operators';
|
||||||
import {MenuService} from '../../menu/menu.service';
|
import {ContextMenuService} from '../../menu/context/context-menu.service';
|
||||||
import {SettingsProvider} from '../../settings/settings.provider';
|
import {SettingsProvider} from '../../settings/settings.provider';
|
||||||
import {DataProvider} from '../data.provider';
|
import {DataProvider} from '../data.provider';
|
||||||
|
|
||||||
@@ -34,6 +34,7 @@ import {DataProvider} from '../data.provider';
|
|||||||
@Component({
|
@Component({
|
||||||
selector: 'stapps-data-list',
|
selector: 'stapps-data-list',
|
||||||
templateUrl: 'data-list.html',
|
templateUrl: 'data-list.html',
|
||||||
|
providers: [ContextMenuService],
|
||||||
})
|
})
|
||||||
export class DataListComponent implements OnInit {
|
export class DataListComponent implements OnInit {
|
||||||
|
|
||||||
@@ -50,7 +51,7 @@ export class DataListComponent implements OnInit {
|
|||||||
/**
|
/**
|
||||||
* Container for queried things
|
* Container for queried things
|
||||||
*/
|
*/
|
||||||
items: SCThing[];
|
items: Promise<SCThing[]>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Page size of queries
|
* Page size of queries
|
||||||
@@ -77,22 +78,27 @@ export class DataListComponent implements OnInit {
|
|||||||
*/
|
*/
|
||||||
sortQuery: SCSearchSort | undefined;
|
sortQuery: SCSearchSort | undefined;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Array of all subscriptions to Observables
|
||||||
|
*/
|
||||||
|
subscriptions: Subscription[] = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param alertController AlertController
|
* @param alertController AlertController
|
||||||
* @param dataProvider DataProvider
|
* @param dataProvider DataProvider
|
||||||
* @param menuService MenuService
|
* @param contextMenuService ContextMenuService
|
||||||
* @param settingsProvider SettingsProvider
|
* @param settingsProvider SettingsProvider
|
||||||
* @param logger An angular logger
|
* @param logger An angular logger
|
||||||
*/
|
*/
|
||||||
constructor(
|
constructor(
|
||||||
private readonly alertController: AlertController,
|
protected readonly alertController: AlertController,
|
||||||
private readonly dataProvider: DataProvider,
|
protected dataProvider: DataProvider,
|
||||||
private readonly menuService: MenuService,
|
protected readonly contextMenuService: ContextMenuService,
|
||||||
private readonly settingsProvider: SettingsProvider,
|
protected readonly settingsProvider: SettingsProvider,
|
||||||
private readonly logger: NGXLogger,
|
protected readonly logger: NGXLogger,
|
||||||
) {
|
) {
|
||||||
this.queryTextChanged
|
this.subscriptions.push(this.queryTextChanged
|
||||||
.pipe(
|
.pipe(
|
||||||
debounceTime(this.searchQueryDueTime),
|
debounceTime(this.searchQueryDueTime),
|
||||||
distinctUntilChanged())
|
distinctUntilChanged())
|
||||||
@@ -100,30 +106,32 @@ export class DataListComponent implements OnInit {
|
|||||||
this.from = 0;
|
this.from = 0;
|
||||||
this.queryText = model;
|
this.queryText = model;
|
||||||
this.fetchAndUpdateItems();
|
this.fetchAndUpdateItems();
|
||||||
});
|
}));
|
||||||
|
|
||||||
this.menuService.filterQueryChanged$.subscribe((query) => {
|
this.subscriptions.push(this.contextMenuService.filterQueryChanged$.subscribe((query) => {
|
||||||
this.filterQuery = query;
|
this.filterQuery = query;
|
||||||
|
this.from = 0;
|
||||||
this.fetchAndUpdateItems();
|
this.fetchAndUpdateItems();
|
||||||
});
|
}));
|
||||||
this.menuService.sortQueryChanged$.subscribe((query) => {
|
this.subscriptions.push(this.contextMenuService.sortQueryChanged$.subscribe((query) => {
|
||||||
this.sortQuery = query;
|
this.sortQuery = query;
|
||||||
|
this.from = 0;
|
||||||
this.fetchAndUpdateItems();
|
this.fetchAndUpdateItems();
|
||||||
});
|
}));
|
||||||
|
|
||||||
this.fetchAndUpdateItems();
|
this.fetchAndUpdateItems();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Subscribe to 'settings.changed' events
|
* Subscribe to 'settings.changed' events
|
||||||
*/
|
*/
|
||||||
this.settingsProvider.settingsActionChanged$.subscribe(({type, payload}) => {
|
this.subscriptions.push(this.settingsProvider.settingsActionChanged$.subscribe(({type, payload}) => {
|
||||||
if (type === 'stapps.settings.changed') {
|
if (type === 'stapps.settings.changed') {
|
||||||
const {category, name, value} = payload!;
|
const {category, name, value} = payload!;
|
||||||
this.logger.log(`received event "settings.changed" with category:
|
this.logger.log(`received event "settings.changed" with category:
|
||||||
${category}, name: ${name}, value: ${JSON.stringify(value)}`);
|
${category}, name: ${name}, value: ${JSON.stringify(value)}`);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -131,7 +139,7 @@ export class DataListComponent implements OnInit {
|
|||||||
*
|
*
|
||||||
* @param append If true fetched data gets appended to existing, override otherwise (default false)
|
* @param append If true fetched data gets appended to existing, override otherwise (default false)
|
||||||
*/
|
*/
|
||||||
private async fetchAndUpdateItems(append = false): Promise<void> {
|
protected async fetchAndUpdateItems(append = false): Promise<void> {
|
||||||
// build query search options
|
// build query search options
|
||||||
const searchOptions: SCSearchQuery = {
|
const searchOptions: SCSearchQuery = {
|
||||||
from: this.from,
|
from: this.from,
|
||||||
@@ -154,17 +162,17 @@ export class DataListComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return this.dataProvider.search(searchOptions)
|
return this.dataProvider.search(searchOptions)
|
||||||
.then((result) => {
|
.then(async (result) => {
|
||||||
if (append) {
|
if (append) {
|
||||||
|
let items = await this.items;
|
||||||
// append results
|
// append results
|
||||||
this.items = this.items.concat(result.data);
|
items = items.concat(result.data);
|
||||||
|
this.items = (async () => items)();
|
||||||
} else {
|
} else {
|
||||||
// override items with results
|
// override items with results
|
||||||
this.items = result.data;
|
this.items = (async () => {
|
||||||
}
|
this.updateContextFilter(result.facets);
|
||||||
// update filter options if result contains facets
|
return result.data} )();
|
||||||
if (typeof result.facets !== 'undefined') {
|
|
||||||
this.updateContextFilter(result.facets);
|
|
||||||
}
|
}
|
||||||
}, async (err) => {
|
}, async (err) => {
|
||||||
const alert: HTMLIonAlertElement = await this.alertController.create({
|
const alert: HTMLIonAlertElement = await this.alertController.create({
|
||||||
@@ -188,11 +196,10 @@ export class DataListComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialises the sort context of menuService
|
* Initialises the possible sort options in ContextMenuService
|
||||||
*/
|
*/
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
// initialise sort option for context menu
|
this.contextMenuService.setContextSort({
|
||||||
this.menuService.setContextSort({
|
|
||||||
name: 'sort',
|
name: 'sort',
|
||||||
reversed: false,
|
reversed: false,
|
||||||
value: 'relevance',
|
value: 'relevance',
|
||||||
@@ -213,6 +220,15 @@ export class DataListComponent implements OnInit {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unsubscribe from Observables
|
||||||
|
*/
|
||||||
|
ngOnDestroy() {
|
||||||
|
for (let sub of this.subscriptions) {
|
||||||
|
sub.unsubscribe();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Search event of search bar
|
* Search event of search bar
|
||||||
*/
|
*/
|
||||||
@@ -221,9 +237,9 @@ export class DataListComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates context filter in menuService with facets
|
* Updates the possible filter options in ContextMenuService with facets
|
||||||
*/
|
*/
|
||||||
updateContextFilter(facets: SCFacet[]) {
|
updateContextFilter(facets: SCFacet[]) {
|
||||||
this.menuService.updateContextFilter(facets);
|
this.contextMenuService.updateContextFilter(facets);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
<ion-menu-button></ion-menu-button>
|
<ion-menu-button></ion-menu-button>
|
||||||
</ion-buttons>
|
</ion-buttons>
|
||||||
<ion-buttons slot="end">
|
<ion-buttons slot="end">
|
||||||
<ion-menu-button menu="context">
|
<ion-menu-button menu="context" auto-hide="false">
|
||||||
<ion-icon name="options"></ion-icon>
|
<ion-icon name="options"></ion-icon>
|
||||||
</ion-menu-button>
|
</ion-menu-button>
|
||||||
</ion-buttons>
|
</ion-buttons>
|
||||||
@@ -17,13 +17,15 @@
|
|||||||
</ion-toolbar>
|
</ion-toolbar>
|
||||||
</ion-header>
|
</ion-header>
|
||||||
|
|
||||||
<ion-content id="data-list">
|
<ion-content id="data-list">
|
||||||
<ion-list *ngIf="items">
|
<div *ngIf="items | async as items; else loading">
|
||||||
<stapps-data-list-item [item]="item" *ngFor="let item of items"></stapps-data-list-item>
|
<ion-list>
|
||||||
</ion-list>
|
<stapps-data-list-item [item]="item" *ngFor="let item of items"></stapps-data-list-item>
|
||||||
<ion-list *ngIf="!items">
|
</ion-list>
|
||||||
|
</div>
|
||||||
|
<ng-template #loading>
|
||||||
<stapps-skeleton-list-item *ngFor="let skeleton of [1, 2, 3, 4, 5]"></stapps-skeleton-list-item>
|
<stapps-skeleton-list-item *ngFor="let skeleton of [1, 2, 3, 4, 5]"></stapps-skeleton-list-item>
|
||||||
</ion-list>
|
</ng-template>
|
||||||
<ion-infinite-scroll (ionInfinite)="loadMore($event)">
|
<ion-infinite-scroll (ionInfinite)="loadMore($event)">
|
||||||
<ion-infinite-scroll-content></ion-infinite-scroll-content>
|
<ion-infinite-scroll-content></ion-infinite-scroll-content>
|
||||||
</ion-infinite-scroll>
|
</ion-infinite-scroll>
|
||||||
|
|||||||
@@ -0,0 +1,126 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2018-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 {Component} from '@angular/core';
|
||||||
|
import {
|
||||||
|
SCSearchQuery, SCThing,
|
||||||
|
} from '@openstapps/core';
|
||||||
|
import {DataListComponent} from './data-list.component';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODO
|
||||||
|
*/
|
||||||
|
@Component({
|
||||||
|
selector: 'stapps-data-list',
|
||||||
|
templateUrl: 'data-list.html',
|
||||||
|
})
|
||||||
|
export class FoodDataListComponent extends DataListComponent {
|
||||||
|
/**
|
||||||
|
* Fetches items with set query configuration
|
||||||
|
*
|
||||||
|
* @param append If true fetched data gets appended to existing, override otherwise (default false)
|
||||||
|
*/
|
||||||
|
protected async fetchAndUpdateItems(append = false): Promise<void> {
|
||||||
|
try {
|
||||||
|
// build query search options
|
||||||
|
const searchOptions: SCSearchQuery = {
|
||||||
|
from: this.from,
|
||||||
|
size: this.pageSize,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (this.queryText && this.queryText.length > 0) {
|
||||||
|
// add query string
|
||||||
|
searchOptions.query = this.queryText;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.sortQuery) {
|
||||||
|
// add query sorting
|
||||||
|
searchOptions.sort = [this.sortQuery];
|
||||||
|
}
|
||||||
|
|
||||||
|
searchOptions.filter = {
|
||||||
|
arguments: {
|
||||||
|
filters: [
|
||||||
|
{
|
||||||
|
arguments: {
|
||||||
|
field: 'categories',
|
||||||
|
value: 'canteen',
|
||||||
|
},
|
||||||
|
type: 'value',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
arguments: {
|
||||||
|
field: 'categories',
|
||||||
|
value: 'student canteen',
|
||||||
|
},
|
||||||
|
type: 'value',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
arguments: {
|
||||||
|
field: 'categories',
|
||||||
|
value: 'cafe',
|
||||||
|
},
|
||||||
|
type: 'value',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
arguments: {
|
||||||
|
field: 'categories',
|
||||||
|
value: 'restaurant',
|
||||||
|
},
|
||||||
|
type: 'value',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
operation: 'or',
|
||||||
|
},
|
||||||
|
type: 'boolean',
|
||||||
|
};
|
||||||
|
|
||||||
|
if (this.filterQuery !== undefined) {
|
||||||
|
searchOptions.filter = {
|
||||||
|
arguments: {
|
||||||
|
filters: [
|
||||||
|
searchOptions.filter,
|
||||||
|
this.filterQuery,
|
||||||
|
],
|
||||||
|
operation: 'and',
|
||||||
|
},
|
||||||
|
type: 'boolean',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await this.dataProvider.search(searchOptions);
|
||||||
|
|
||||||
|
this.items = (async () => {
|
||||||
|
this.updateContextFilter(result.facets);
|
||||||
|
let items: SCThing[];
|
||||||
|
if(append) {
|
||||||
|
items = (await this.items).concat(result.data);
|
||||||
|
} else {
|
||||||
|
items = result.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
return items;
|
||||||
|
})();
|
||||||
|
} catch (err) {
|
||||||
|
const alert: HTMLIonAlertElement = await this.alertController.create({
|
||||||
|
buttons: ['Dismiss'],
|
||||||
|
header: 'Error',
|
||||||
|
subHeader: err.message,
|
||||||
|
});
|
||||||
|
|
||||||
|
await alert.present();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -14,12 +14,14 @@
|
|||||||
*/
|
*/
|
||||||
import {Component, Input} from '@angular/core';
|
import {Component, Input} from '@angular/core';
|
||||||
import {SCBuilding, SCFloor, SCPointOfInterest, SCRoom, SCThing, SCTranslations} from '@openstapps/core';
|
import {SCBuilding, SCFloor, SCPointOfInterest, SCRoom, SCThing, SCTranslations} from '@openstapps/core';
|
||||||
import {SCThingTranslator} from '@openstapps/core';
|
import {SCThings, SCThingTranslator} from '@openstapps/core';
|
||||||
|
import {DataProvider} from '../../data.provider';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TODO
|
* TODO
|
||||||
*/
|
*/
|
||||||
@Component({
|
@Component({
|
||||||
|
providers: [ DataProvider ],
|
||||||
selector: 'stapps-place-detail-content',
|
selector: 'stapps-place-detail-content',
|
||||||
templateUrl: 'place-detail-content.html',
|
templateUrl: 'place-detail-content.html',
|
||||||
})
|
})
|
||||||
@@ -47,4 +49,26 @@ export class PlaceDetailContentComponent {
|
|||||||
constructor() {
|
constructor() {
|
||||||
this.translator = new SCThingTranslator(this.language);
|
this.translator = new SCThingTranslator(this.language);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODO
|
||||||
|
*
|
||||||
|
* @param item TODO
|
||||||
|
*/
|
||||||
|
// tslint:disable-next-line:completed-docs prefer-function-over-method
|
||||||
|
hasCategories(item: SCThings): item is SCThings & { categories: string[]; } {
|
||||||
|
// tslint:disable-next-line:completed-docs
|
||||||
|
return typeof (item as { categories: string[]; }).categories !== 'undefined';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper function as 'typeof' is not accessible in HTML
|
||||||
|
*
|
||||||
|
* @param item TODO
|
||||||
|
*/
|
||||||
|
isMensaThing(item: SCThings): boolean {
|
||||||
|
return this.hasCategories(item) &&
|
||||||
|
((item.categories as string[]).includes('canteen') || (item.categories as string[]).includes('cafe')
|
||||||
|
|| (item.categories as string[]).includes('student canteen') || (item.categories as string[]).includes('restaurant'));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
<stapps-place-mensa-detail-content [item]="item" [language]="language" *ngIf="isMensaThing(item)"></stapps-place-mensa-detail-content>
|
||||||
<ng-container *ngIf="item.type !== 'floor'">
|
<ng-container *ngIf="item.type !== 'floor'">
|
||||||
<stapps-simple-card *ngIf="item.type !== 'floor' && item.categories" [title]="'Categories'" [content]="translator.translate(item).categories()"></stapps-simple-card>
|
<stapps-simple-card *ngIf="item.type !== 'floor' && item.categories" [title]="'Categories'" [content]="translator.translate(item).categories()"></stapps-simple-card>
|
||||||
<stapps-address-detail *ngIf="item.type !== 'floor' && item.address" [address]="item.address"></stapps-address-detail>
|
<stapps-address-detail *ngIf="item.type !== 'floor' && item.address" [address]="item.address"></stapps-address-detail>
|
||||||
|
|||||||
@@ -0,0 +1,132 @@
|
|||||||
|
/*
|
||||||
|
* 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 moment, {Moment} from 'moment';
|
||||||
|
|
||||||
|
import {AfterViewInit, ChangeDetectorRef, Component, Input} from '@angular/core';
|
||||||
|
import {SCDish, SCPlace, SCThing, SCThingTranslator, SCTranslations} from '@openstapps/core';
|
||||||
|
import {PlaceMensaService} from './place-mensa-service';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODO
|
||||||
|
*/
|
||||||
|
@Component({
|
||||||
|
providers: [PlaceMensaService],
|
||||||
|
selector: 'stapps-place-mensa-detail-content',
|
||||||
|
templateUrl: 'place-mensa.html',
|
||||||
|
})
|
||||||
|
export class PlaceMensaDetailComponent implements AfterViewInit {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODO
|
||||||
|
*/
|
||||||
|
dishes: Promise<Record<string, SCDish[]>> | null = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* number of days to display mensa menus for
|
||||||
|
*/
|
||||||
|
displayRange = 5;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODO
|
||||||
|
*/
|
||||||
|
@Input() item: SCPlace;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODO
|
||||||
|
*/
|
||||||
|
@Input() language: keyof SCTranslations<SCThing>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODO
|
||||||
|
*/
|
||||||
|
selectedDay: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* First day to display menu items for
|
||||||
|
*/
|
||||||
|
startingDay: Moment;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODO
|
||||||
|
*/
|
||||||
|
translator: SCThingTranslator;
|
||||||
|
|
||||||
|
|
||||||
|
constructor(private mensaService: PlaceMensaService, private changeDectectorRef: ChangeDetectorRef) {
|
||||||
|
// TODO: translation
|
||||||
|
this.translator = new SCThingTranslator(this.language);
|
||||||
|
this.startingDay = moment();
|
||||||
|
this.startingDay.hour(0);
|
||||||
|
this.startingDay.minute(0);
|
||||||
|
this.startingDay.millisecond(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODO
|
||||||
|
*/
|
||||||
|
ngAfterViewInit() {
|
||||||
|
this.dishes = this.mensaService.getAllDishes(this.item)
|
||||||
|
.then((dishesResult) => {
|
||||||
|
const out = this.splitDishes(dishesResult, this.item, this.startingDay, this.displayRange);
|
||||||
|
for (const key in out) {
|
||||||
|
if (out.hasOwnProperty(key)) {
|
||||||
|
this.selectedDay = key;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.changeDectectorRef.detectChanges();
|
||||||
|
|
||||||
|
return out;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Splits a list of dishes with availability start and end into multiple lists by day
|
||||||
|
*/
|
||||||
|
// tslint:disable-next-line:prefer-function-over-method
|
||||||
|
splitDishes(dishes: SCDish[], place: SCPlace, startingDay: Moment, displayRange: number): Record<string, SCDish[]> {
|
||||||
|
const out: Record<string, SCDish[]> = {};
|
||||||
|
|
||||||
|
for (let i = 0; i < displayRange; i++) {
|
||||||
|
const nextDay: Moment = moment(startingDay)
|
||||||
|
.add(1, 'days');
|
||||||
|
const selectedDishes: SCDish[] = [];
|
||||||
|
for (const dish of dishes) {
|
||||||
|
// go through all offers
|
||||||
|
if (dish.offers === undefined) { continue; }
|
||||||
|
for (const offer of dish.offers) {
|
||||||
|
if (offer.inPlace === undefined || offer.inPlace.uid !== place.uid) { continue; }
|
||||||
|
// get availabilities
|
||||||
|
const availabilityStarts = offer.availabilityStarts === undefined ? undefined : moment(offer.availabilityStarts);
|
||||||
|
const availabilityEnds = offer.availabilityEnds === undefined ? undefined : moment(offer.availabilityEnds);
|
||||||
|
|
||||||
|
if ((availabilityStarts === undefined || availabilityStarts.isBefore(nextDay))
|
||||||
|
&& (availabilityEnds === undefined || availabilityEnds.isAfter(startingDay))) {
|
||||||
|
selectedDishes.push(dish);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (selectedDishes.length) {
|
||||||
|
out[startingDay.format('YYYY-MM-DD')] = selectedDishes;
|
||||||
|
}
|
||||||
|
// tslint:disable-next-line:no-parameter-reassignment
|
||||||
|
startingDay = nextDay;
|
||||||
|
}
|
||||||
|
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
/*
|
||||||
|
* 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 {Injectable} from '@angular/core';
|
||||||
|
import {SCDish, SCPlace, SCThingType} from '@openstapps/core';
|
||||||
|
import {DataProvider} from '../../../../data.provider';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODO
|
||||||
|
*/
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root',
|
||||||
|
})
|
||||||
|
export class PlaceMensaService {
|
||||||
|
constructor(private dataProvider: DataProvider) { }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetches all dishes for this building
|
||||||
|
*/
|
||||||
|
async getAllDishes(place: SCPlace): Promise<SCDish[]> {
|
||||||
|
// use filter to get all dishes with the building's uid in one of the offer's inPlace field
|
||||||
|
// TODO: make sure this actually works with ES
|
||||||
|
const result = await this.dataProvider.search({
|
||||||
|
filter: {
|
||||||
|
arguments: {
|
||||||
|
filters: [
|
||||||
|
{
|
||||||
|
arguments: {
|
||||||
|
field: 'offers.inPlace.uid',
|
||||||
|
value: place.uid,
|
||||||
|
},
|
||||||
|
type: 'value',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
arguments: {
|
||||||
|
field: 'type',
|
||||||
|
value: SCThingType.Dish,
|
||||||
|
},
|
||||||
|
type: 'value',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
operation: 'and',
|
||||||
|
},
|
||||||
|
type: 'boolean',
|
||||||
|
},
|
||||||
|
size: 1000,
|
||||||
|
});
|
||||||
|
|
||||||
|
return result.data as SCDish[];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
<ng-container>
|
||||||
|
<div *ngIf="dishes | async as dishes; else loading">
|
||||||
|
<ion-segment [(ngModel)]="selectedDay">
|
||||||
|
<ion-segment-button *ngFor="let day of dishes | keyvalue" [value]="day.key">
|
||||||
|
<ion-label>{{day.key | amParse:'YYYY-MM-DD' | amDateFormat:'dddd, L'}}</ion-label>
|
||||||
|
</ion-segment-button>
|
||||||
|
</ion-segment>
|
||||||
|
<div [ngSwitch]="selectedDay">
|
||||||
|
<div *ngFor="let day of dishes | keyvalue">
|
||||||
|
<ion-list *ngSwitchCase="day.key">
|
||||||
|
<stapps-data-list-item [item]="dish" *ngFor="let dish of day.value"></stapps-data-list-item>
|
||||||
|
</ion-list>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<ng-template #loading>
|
||||||
|
<ion-segment>
|
||||||
|
<stapps-skeleton-segment-button *ngFor="let skeleton of [1, 2, 3]"></stapps-skeleton-segment-button>
|
||||||
|
</ion-segment>
|
||||||
|
<ion-list>
|
||||||
|
<stapps-skeleton-list-item *ngFor="let skeleton of [1, 2]"></stapps-skeleton-list-item>
|
||||||
|
</ion-list>
|
||||||
|
</ng-template>
|
||||||
|
</ng-container>
|
||||||
@@ -21,7 +21,7 @@ import {TranslateModule,} from '@ngx-translate/core';
|
|||||||
import {SCFacet, SCThingType} from '@openstapps/core';
|
import {SCFacet, SCThingType} from '@openstapps/core';
|
||||||
import {ContextMenuComponent} from './context-menu.component';
|
import {ContextMenuComponent} from './context-menu.component';
|
||||||
import {SettingsModule} from '../../settings/settings.module';
|
import {SettingsModule} from '../../settings/settings.module';
|
||||||
import {MenuService} from '../menu.service';
|
import {ContextMenuService} from '../context/context-menu.service';
|
||||||
import {FilterContext, SortContext} from './context-type';
|
import {FilterContext, SortContext} from './context-type';
|
||||||
|
|
||||||
describe('ContextMenuComponent', async () => {
|
describe('ContextMenuComponent', async () => {
|
||||||
@@ -36,7 +36,7 @@ describe('ContextMenuComponent', async () => {
|
|||||||
ChildrenOutletContexts,
|
ChildrenOutletContexts,
|
||||||
Location,
|
Location,
|
||||||
UrlSerializer,
|
UrlSerializer,
|
||||||
MenuService,
|
ContextMenuService,
|
||||||
{provide: LocationStrategy, useClass: PathLocationStrategy},
|
{provide: LocationStrategy, useClass: PathLocationStrategy},
|
||||||
{provide: APP_BASE_HREF, useValue: '/'},
|
{provide: APP_BASE_HREF, useValue: '/'},
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -20,8 +20,9 @@ import {
|
|||||||
SCThingType,
|
SCThingType,
|
||||||
SCTranslations,
|
SCTranslations,
|
||||||
} from '@openstapps/core';
|
} from '@openstapps/core';
|
||||||
import {MenuService} from '../menu.service';
|
import {ContextMenuService} from './context-menu.service';
|
||||||
import {FilterContext, SortContext, SortContextOption} from './context-type';
|
import {FilterContext, SortContext, SortContextOption} from './context-type';
|
||||||
|
import {Subscription} from 'rxjs';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The context menu
|
* The context menu
|
||||||
@@ -68,30 +69,45 @@ export class ContextMenuComponent {
|
|||||||
*/
|
*/
|
||||||
translator: SCThingTranslator;
|
translator: SCThingTranslator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Array of all Subscriptions
|
||||||
|
*/
|
||||||
|
subscriptions: Subscription[] = [];
|
||||||
|
|
||||||
|
|
||||||
constructor(private translateService: TranslateService,
|
constructor(private translateService: TranslateService,
|
||||||
private readonly menuService: MenuService) {
|
private readonly contextMenuService: ContextMenuService) {
|
||||||
this.language = this.translateService.currentLang as keyof SCTranslations<SCLanguage>;
|
this.language = this.translateService.currentLang as keyof SCTranslations<SCLanguage>;
|
||||||
this.translator = new SCThingTranslator(this.language);
|
this.translator = new SCThingTranslator(this.language);
|
||||||
|
|
||||||
this.translateService.onLangChange.subscribe((event: LangChangeEvent) => {
|
this.subscriptions.push(this.translateService.onLangChange.subscribe((event: LangChangeEvent) => {
|
||||||
this.language = event.lang as keyof SCTranslations<SCLanguage>;
|
this.language = event.lang as keyof SCTranslations<SCLanguage>;
|
||||||
this.translator = new SCThingTranslator(this.language);
|
this.translator = new SCThingTranslator(this.language);
|
||||||
});
|
}));
|
||||||
|
|
||||||
this.menuService.filterContextChanged$.subscribe((filterContext) => {
|
this.subscriptions.push(this.contextMenuService.filterContextChanged$.subscribe((filterContext) => {
|
||||||
this.filterOption = filterContext;
|
this.filterOption = filterContext;
|
||||||
});
|
}));
|
||||||
|
|
||||||
this.menuService.sortOptions.subscribe((sortContext) => {
|
this.subscriptions.push(this.contextMenuService.sortOptions.subscribe((sortContext) => {
|
||||||
this.sortOption = sortContext;
|
this.sortOption = sortContext;
|
||||||
});
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unsubscribe from Observables
|
||||||
|
*/
|
||||||
|
ngOnDestroy() {
|
||||||
|
for (let sub of this.subscriptions) {
|
||||||
|
sub.unsubscribe();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets selected filter options and updates listener
|
* Sets selected filter options and updates listener
|
||||||
*/
|
*/
|
||||||
filterChanged = () => {
|
filterChanged = () => {
|
||||||
this.menuService.contextFilterChanged(this.filterOption);
|
this.contextMenuService.contextFilterChanged(this.filterOption);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -117,7 +133,7 @@ export class ContextMenuComponent {
|
|||||||
option.options.forEach((filterFacet) => filterFacet.buckets.forEach((filterBucket) => {
|
option.options.forEach((filterFacet) => filterFacet.buckets.forEach((filterBucket) => {
|
||||||
filterBucket.checked = false;
|
filterBucket.checked = false;
|
||||||
}));
|
}));
|
||||||
this.menuService.contextFilterChanged(this.filterOption);
|
this.contextMenuService.contextFilterChanged(this.filterOption);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -134,6 +150,6 @@ export class ContextMenuComponent {
|
|||||||
option.reversed = false;
|
option.reversed = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.menuService.contextSortChanged(option);
|
this.contextMenuService.contextSortChanged(option);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+6
-6
@@ -1,17 +1,17 @@
|
|||||||
import {TestBed} from '@angular/core/testing';
|
import {TestBed} from '@angular/core/testing';
|
||||||
|
|
||||||
import {MenuService} from './menu.service';
|
import {ContextMenuService} from './context-menu.service';
|
||||||
import {SCFacet} from '@openstapps/core';
|
import {SCFacet} from '@openstapps/core';
|
||||||
import {FilterContext, SortContext} from './context/context-type';
|
import {FilterContext, SortContext} from './context-type';
|
||||||
|
|
||||||
describe('MenuService', () => {
|
describe('ContextMenuService', () => {
|
||||||
let service: MenuService;
|
let service: ContextMenuService;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
providers: [MenuService]
|
providers: [ContextMenuService]
|
||||||
});
|
});
|
||||||
service = TestBed.get(MenuService);
|
service = TestBed.get(ContextMenuService);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should be created', () => {
|
it('should be created', () => {
|
||||||
+15
-5
@@ -15,13 +15,13 @@
|
|||||||
import {Injectable} from '@angular/core';
|
import {Injectable} from '@angular/core';
|
||||||
import {SCFacet, SCFacetBucket, SCSearchFilter, SCSearchSort} from '@openstapps/core';
|
import {SCFacet, SCFacetBucket, SCSearchFilter, SCSearchSort} from '@openstapps/core';
|
||||||
import {Subject} from 'rxjs';
|
import {Subject} from 'rxjs';
|
||||||
import {FilterBucket, FilterContext, FilterFacet, SortContext} from './context/context-type';
|
import {FilterBucket, FilterContext, FilterFacet, SortContext} from './context-type';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* MenuService provides bidirectional communication of context menu options and search queries
|
* ContextMenuService provides bidirectional communication of context menu options and search queries
|
||||||
*/
|
*/
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class MenuService {
|
export class ContextMenuService {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Local filter context object
|
* Local filter context object
|
||||||
@@ -82,9 +82,10 @@ export class MenuService {
|
|||||||
const filters: SCSearchFilter[] = [];
|
const filters: SCSearchFilter[] = [];
|
||||||
|
|
||||||
filterContext.options.forEach((filterFacet) => {
|
filterContext.options.forEach((filterFacet) => {
|
||||||
|
const optionFilters: SCSearchFilter[] = [];
|
||||||
filterFacet.buckets.forEach((filterBucket) => {
|
filterFacet.buckets.forEach((filterBucket) => {
|
||||||
if (filterBucket.checked) {
|
if (filterBucket.checked) {
|
||||||
filters.push(
|
optionFilters.push(
|
||||||
{
|
{
|
||||||
arguments: {
|
arguments: {
|
||||||
field: filterFacet.field,
|
field: filterFacet.field,
|
||||||
@@ -94,13 +95,22 @@ export class MenuService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
if (optionFilters.length > 0) {
|
||||||
|
filters.push({
|
||||||
|
arguments: {
|
||||||
|
filters: optionFilters,
|
||||||
|
operation: 'or',
|
||||||
|
},
|
||||||
|
type: 'boolean',
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (filters.length > 0) {
|
if (filters.length > 0) {
|
||||||
return {
|
return {
|
||||||
arguments: {
|
arguments: {
|
||||||
filters: filters,
|
filters: filters,
|
||||||
operation: 'or',
|
operation: 'and',
|
||||||
},
|
},
|
||||||
type: 'boolean',
|
type: 'boolean',
|
||||||
};
|
};
|
||||||
@@ -20,7 +20,7 @@ import {IonicModule} from '@ionic/angular';
|
|||||||
import {TranslateModule} from '@ngx-translate/core';
|
import {TranslateModule} from '@ngx-translate/core';
|
||||||
import {SettingsModule} from '../settings/settings.module';
|
import {SettingsModule} from '../settings/settings.module';
|
||||||
import {ContextMenuComponent} from './context/context-menu.component';
|
import {ContextMenuComponent} from './context/context-menu.component';
|
||||||
import {MenuService} from './menu.service';
|
import {ContextMenuService} from './context/context-menu.service';
|
||||||
import {NavigationComponent} from './navigation/navigation.component';
|
import {NavigationComponent} from './navigation/navigation.component';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -44,7 +44,7 @@ import {NavigationComponent} from './navigation/navigation.component';
|
|||||||
SettingsModule,
|
SettingsModule,
|
||||||
],
|
],
|
||||||
providers: [
|
providers: [
|
||||||
MenuService,
|
ContextMenuService,
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
export class MenuModule {}
|
export class MenuModule {}
|
||||||
|
|||||||
Reference in New Issue
Block a user