mirror of
https://gitlab.com/openstapps/openstapps.git
synced 2026-05-14 00:49:23 +00:00
refactor: move app to monorepo
This commit is contained in:
22
frontend/app/src/app/_helpers/collections/chunk.spec.ts
Normal file
22
frontend/app/src/app/_helpers/collections/chunk.spec.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* 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 {chunk} from './chunk';
|
||||
|
||||
describe('chunk', function () {
|
||||
it('should chunk items in the correct sizes', function () {
|
||||
expect(chunk([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], 3)).toEqual([[1, 2, 3], [4, 5, 6], [7, 8, 9], [10]]);
|
||||
});
|
||||
});
|
||||
28
frontend/app/src/app/_helpers/collections/chunk.ts
Normal file
28
frontend/app/src/app/_helpers/collections/chunk.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Chunk array into smaller arrays of a specified size.
|
||||
*
|
||||
* @param array The array to chunk.
|
||||
* @param chunkSize The size of each chunk.
|
||||
*/
|
||||
export function chunk<T>(array: T[], chunkSize = 1): T[][] {
|
||||
const arrayCopy = [...array];
|
||||
const out: T[][] = [];
|
||||
if (chunkSize <= 0) return out;
|
||||
while (arrayCopy.length > 0) out.push(arrayCopy.splice(0, chunkSize));
|
||||
return out;
|
||||
}
|
||||
25
frontend/app/src/app/_helpers/collections/difference.spec.ts
Normal file
25
frontend/app/src/app/_helpers/collections/difference.spec.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* 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 {differenceBy} from './difference';
|
||||
|
||||
describe('differenceBy', function () {
|
||||
it('should return the difference of two arrays', function () {
|
||||
const a = [1, 2, 3, 4, 5];
|
||||
const b = [1, 2, 3];
|
||||
|
||||
expect(differenceBy(a, b, it => it)).toEqual([4, 5]);
|
||||
});
|
||||
});
|
||||
23
frontend/app/src/app/_helpers/collections/difference.ts
Normal file
23
frontend/app/src/app/_helpers/collections/difference.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Returns the difference between two arrays.
|
||||
*/
|
||||
export function differenceBy<T>(a: T[], b: T[], transform: (item: T) => unknown) {
|
||||
const disallowed = new Set(b.map(transform));
|
||||
|
||||
return a.filter(item => !disallowed.has(transform(item)));
|
||||
}
|
||||
39
frontend/app/src/app/_helpers/collections/get.spec.ts
Normal file
39
frontend/app/src/app/_helpers/collections/get.spec.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* 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 {get} from './get';
|
||||
|
||||
describe('get', function () {
|
||||
it('should get a simple path', function () {
|
||||
const object = {
|
||||
a: {
|
||||
b: {
|
||||
c: 'd',
|
||||
},
|
||||
},
|
||||
};
|
||||
expect(get(object, 'a.b.c')).toBe('d');
|
||||
});
|
||||
|
||||
it('should return undefined for a non-existent path', function () {
|
||||
const object = {
|
||||
a: {
|
||||
b: {
|
||||
c: 'd',
|
||||
},
|
||||
},
|
||||
};
|
||||
expect(get(object, 'a.b.c.d')).toBeUndefined();
|
||||
});
|
||||
});
|
||||
30
frontend/app/src/app/_helpers/collections/get.ts
Normal file
30
frontend/app/src/app/_helpers/collections/get.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Gets a value from a nested object.
|
||||
* The path must be key names separated by dots.
|
||||
* If the path doesn't exist, undefined is returned.
|
||||
*/
|
||||
export function get<U = unknown>(object: object, path: string): U {
|
||||
return path.split('.').reduce(
|
||||
(accumulator, current) =>
|
||||
accumulator?.hasOwnProperty(current)
|
||||
? // eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
(accumulator as any)[current]
|
||||
: undefined,
|
||||
object,
|
||||
) as unknown as U;
|
||||
}
|
||||
123
frontend/app/src/app/_helpers/collections/group-by.spec.ts
Normal file
123
frontend/app/src/app/_helpers/collections/group-by.spec.ts
Normal file
@@ -0,0 +1,123 @@
|
||||
/*
|
||||
* 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 {groupBy, groupByStable, groupByProperty} from './group-by';
|
||||
|
||||
describe('groupBy', () => {
|
||||
it('should group an array by a key', () => {
|
||||
const array = [
|
||||
{id: 1, name: 'one'},
|
||||
{id: 2, name: 'two'},
|
||||
{id: 3, name: 'three'},
|
||||
{id: 4, name: 'four'},
|
||||
{id: 5, name: 'five'},
|
||||
];
|
||||
|
||||
const result = groupBy(array, it => it.name);
|
||||
|
||||
expect(result).toEqual({
|
||||
one: [{id: 1, name: 'one'}],
|
||||
two: [{id: 2, name: 'two'}],
|
||||
three: [{id: 3, name: 'three'}],
|
||||
four: [{id: 4, name: 'four'}],
|
||||
five: [{id: 5, name: 'five'}],
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle multiple elements per group', () => {
|
||||
const array = [
|
||||
{id: 1, name: 'one'},
|
||||
{id: 2, name: 'two'},
|
||||
{id: 3, name: 'three'},
|
||||
{id: 4, name: 'four'},
|
||||
{id: 5, name: 'five'},
|
||||
{id: 6, name: 'one'},
|
||||
{id: 7, name: 'two'},
|
||||
{id: 8, name: 'three'},
|
||||
{id: 9, name: 'four'},
|
||||
{id: 10, name: 'five'},
|
||||
];
|
||||
|
||||
const result = groupBy(array, it => it.name);
|
||||
|
||||
expect(result).toEqual({
|
||||
one: [
|
||||
{id: 1, name: 'one'},
|
||||
{id: 6, name: 'one'},
|
||||
],
|
||||
two: [
|
||||
{id: 2, name: 'two'},
|
||||
{id: 7, name: 'two'},
|
||||
],
|
||||
three: [
|
||||
{id: 3, name: 'three'},
|
||||
{id: 8, name: 'three'},
|
||||
],
|
||||
four: [
|
||||
{id: 4, name: 'four'},
|
||||
{id: 9, name: 'four'},
|
||||
],
|
||||
five: [
|
||||
{id: 5, name: 'five'},
|
||||
{id: 10, name: 'five'},
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('groupByStable', () => {
|
||||
const array = [
|
||||
{id: 2, name: 'two'},
|
||||
{id: 4, name: 'three'},
|
||||
{id: 3, name: 'three'},
|
||||
{id: 1, name: 'one'},
|
||||
];
|
||||
const result = groupByStable(array, it => it.name);
|
||||
|
||||
it('should group an array by keys', () => {
|
||||
expect(result.get('one')).toEqual([{id: 1, name: 'one'}]);
|
||||
expect(result.get('two')).toEqual([{id: 2, name: 'two'}]);
|
||||
expect(result.get('three')).toEqual([
|
||||
{id: 4, name: 'three'},
|
||||
{id: 3, name: 'three'},
|
||||
]);
|
||||
});
|
||||
|
||||
it('should provide ordered keys', () => {
|
||||
expect([...result.keys()]).toEqual(['two', 'three', 'one']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('groupByProperty', function () {
|
||||
it('should group by property', () => {
|
||||
const array = [
|
||||
{id: 1, name: 'one'},
|
||||
{id: 2, name: 'two'},
|
||||
{id: 3, name: 'three'},
|
||||
{id: 4, name: 'four'},
|
||||
{id: 5, name: 'five'},
|
||||
];
|
||||
|
||||
const result = groupByProperty(array, 'name');
|
||||
|
||||
expect(result).toEqual({
|
||||
one: [{id: 1, name: 'one'}],
|
||||
two: [{id: 2, name: 'two'}],
|
||||
three: [{id: 3, name: 'three'}],
|
||||
four: [{id: 4, name: 'four'}],
|
||||
five: [{id: 5, name: 'five'}],
|
||||
});
|
||||
});
|
||||
});
|
||||
45
frontend/app/src/app/_helpers/collections/group-by.ts
Normal file
45
frontend/app/src/app/_helpers/collections/group-by.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Group an array by a function
|
||||
*/
|
||||
export function groupBy<T>(collection: T[], group: (item: T) => string | undefined): Record<string, T[]> {
|
||||
return collection.reduce((accumulator: Record<string, T[]>, item) => {
|
||||
const key = group(item) ?? '';
|
||||
accumulator[key] = accumulator[key] ?? [];
|
||||
accumulator[key].push(item);
|
||||
return accumulator;
|
||||
}, {});
|
||||
}
|
||||
|
||||
/**
|
||||
* Group an array by a function (returns a Map, whose keys keep order info of items entry)
|
||||
*/
|
||||
export function groupByStable<T>(collection: T[], group: (item: T) => string | undefined): Map<string, T[]> {
|
||||
return collection.reduce((accumulator: Map<string, T[]>, item) => {
|
||||
const key = group(item) ?? '';
|
||||
accumulator.set(key, accumulator.get(key) ?? []);
|
||||
accumulator.get(key)?.push(item);
|
||||
return accumulator;
|
||||
}, new Map<string, T[]>());
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
export function groupByProperty<T extends object>(collection: T[], property: keyof T): Record<string, T[]> {
|
||||
return groupBy(collection, item => item[property] as unknown as string);
|
||||
}
|
||||
41
frontend/app/src/app/_helpers/collections/key-by.spec.ts
Normal file
41
frontend/app/src/app/_helpers/collections/key-by.spec.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* 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 {keyBy} from './key-by';
|
||||
|
||||
describe('keyBy', function () {
|
||||
it('should key objects', function () {
|
||||
const objects = [
|
||||
{
|
||||
id: 1,
|
||||
name: 'foo',
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: 'bar',
|
||||
},
|
||||
];
|
||||
const result = keyBy(objects, it => it.id);
|
||||
expect(result).toEqual({
|
||||
1: {
|
||||
id: 1,
|
||||
name: 'foo',
|
||||
},
|
||||
2: {
|
||||
id: 2,
|
||||
name: 'bar',
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
27
frontend/app/src/app/_helpers/collections/key-by.ts
Normal file
27
frontend/app/src/app/_helpers/collections/key-by.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Create an object composed of keys generated from the results of running
|
||||
* each element of collection thru iteratee. The corresponding value of
|
||||
* each key is the last element responsible for generating the key. The
|
||||
* iteratee is invoked with one argument: (value).
|
||||
*/
|
||||
export function keyBy<T>(collection: T[], key: (item: T) => string | number): Record<string, T> {
|
||||
return collection.reduce((accumulator, item) => {
|
||||
accumulator[key(item)] = item;
|
||||
return accumulator;
|
||||
}, {} as Record<string | number, T>);
|
||||
}
|
||||
50
frontend/app/src/app/_helpers/collections/map-values.spec.ts
Normal file
50
frontend/app/src/app/_helpers/collections/map-values.spec.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* 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 {mapValues} from './map-values';
|
||||
|
||||
describe('map-values', () => {
|
||||
it('should map values', () => {
|
||||
const object = {
|
||||
a: 1,
|
||||
b: 2,
|
||||
c: 3,
|
||||
};
|
||||
|
||||
const result = mapValues(object, value => value * 2);
|
||||
|
||||
expect(result).toEqual({
|
||||
a: 2,
|
||||
b: 4,
|
||||
c: 6,
|
||||
});
|
||||
});
|
||||
|
||||
it('should not modify the original object', () => {
|
||||
const object = {
|
||||
a: 1,
|
||||
b: 2,
|
||||
c: 3,
|
||||
};
|
||||
|
||||
mapValues(object, value => value * 2);
|
||||
|
||||
expect(object).toEqual({
|
||||
a: 1,
|
||||
b: 2,
|
||||
c: 3,
|
||||
});
|
||||
});
|
||||
});
|
||||
32
frontend/app/src/app/_helpers/collections/map-values.ts
Normal file
32
frontend/app/src/app/_helpers/collections/map-values.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Maps the values of an object to a new object
|
||||
*/
|
||||
export function mapValues<T extends object, U>(
|
||||
object: T,
|
||||
transform: (value: T[keyof T], key: keyof T) => U,
|
||||
): {[key in keyof T]: U} {
|
||||
const result = {} as {[key in keyof T]: U};
|
||||
|
||||
for (const key in object) {
|
||||
if (object.hasOwnProperty(key)) {
|
||||
result[key] = transform(object[key], key);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
44
frontend/app/src/app/_helpers/collections/min.spec.ts
Normal file
44
frontend/app/src/app/_helpers/collections/min.spec.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* 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 {minBy} from './min';
|
||||
|
||||
describe('minBy', function () {
|
||||
it('should pick the minimum value based on transform', function () {
|
||||
expect(
|
||||
minBy(
|
||||
[
|
||||
{id: 1, name: 'A'},
|
||||
{id: 2, name: 'B'},
|
||||
{id: 3, name: 'C'},
|
||||
],
|
||||
it => it.id,
|
||||
),
|
||||
).toEqual({id: 1, name: 'A'});
|
||||
});
|
||||
|
||||
it('should not return undefined if there are other choices', function () {
|
||||
expect(
|
||||
minBy(
|
||||
[
|
||||
{id: undefined, name: 'B'},
|
||||
{id: 1, name: 'A'},
|
||||
{id: undefined, name: 'C'},
|
||||
],
|
||||
it => it.id,
|
||||
),
|
||||
).toEqual({id: 1, name: 'A'});
|
||||
});
|
||||
});
|
||||
23
frontend/app/src/app/_helpers/collections/min.ts
Normal file
23
frontend/app/src/app/_helpers/collections/min.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Returns the minimum value of a collection.
|
||||
*/
|
||||
export function minBy<T>(array: T[], transform: (item: T) => number | undefined): T {
|
||||
const transforms = array.map(transform);
|
||||
const min = Math.min(...(transforms.filter(it => !!it) as number[]));
|
||||
return array.find((_, i) => transforms[i] === min) as T;
|
||||
}
|
||||
23
frontend/app/src/app/_helpers/collections/omit.spec.ts
Normal file
23
frontend/app/src/app/_helpers/collections/omit.spec.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
* 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 {omit} from './omit';
|
||||
|
||||
describe('omit', function () {
|
||||
it('should omit keys', function () {
|
||||
const object = {a: 1, b: 2, c: 3};
|
||||
const result = omit(object, 'a', 'c');
|
||||
expect(result).toEqual({b: 2});
|
||||
});
|
||||
});
|
||||
23
frontend/app/src/app/_helpers/collections/omit.ts
Normal file
23
frontend/app/src/app/_helpers/collections/omit.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Returns a new object without the specified keys.
|
||||
*/
|
||||
export function omit<T extends object, U extends keyof T>(object: T, ...keys: U[]): Omit<T, U> {
|
||||
const out = {...object};
|
||||
for (const key of keys) delete out[key];
|
||||
return out as Exclude<T, U>;
|
||||
}
|
||||
25
frontend/app/src/app/_helpers/collections/partition.spec.ts
Normal file
25
frontend/app/src/app/_helpers/collections/partition.spec.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* 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 {partition} from './partition';
|
||||
|
||||
describe('partition', function () {
|
||||
it('should partition an array', function () {
|
||||
expect(partition([1, 2, 3, 4], it => it % 2 === 0)).toEqual([
|
||||
[2, 4],
|
||||
[1, 3],
|
||||
]);
|
||||
});
|
||||
});
|
||||
28
frontend/app/src/app/_helpers/collections/partition.ts
Normal file
28
frontend/app/src/app/_helpers/collections/partition.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Partitions a list into two lists. One with the elements that satisfy a predicate,
|
||||
* and one with the elements that don't satisfy the predicate.
|
||||
*/
|
||||
export function partition<T>(array: T[], transform: (item: T) => boolean): [T[], T[]] {
|
||||
return array.reduce<[T[], T[]]>(
|
||||
(accumulator, item) => {
|
||||
accumulator[transform(item) ? 0 : 1].push(item);
|
||||
return accumulator;
|
||||
},
|
||||
[[], []],
|
||||
);
|
||||
}
|
||||
23
frontend/app/src/app/_helpers/collections/pick.spec.ts
Normal file
23
frontend/app/src/app/_helpers/collections/pick.spec.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
* 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 {pick} from './pick';
|
||||
|
||||
describe('pick', function () {
|
||||
it('should pick properties', function () {
|
||||
const object = {a: 1, b: 2, c: 3};
|
||||
const result = pick(object, ['a', 'c']);
|
||||
expect(result).toEqual({a: 1, c: 3});
|
||||
});
|
||||
});
|
||||
41
frontend/app/src/app/_helpers/collections/pick.ts
Normal file
41
frontend/app/src/app/_helpers/collections/pick.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Pick a set of properties from an object
|
||||
*/
|
||||
export function pick<T extends object, U extends keyof T>(object: T, keys: U[]): Pick<T, U> {
|
||||
return keys.reduce((accumulator, key) => {
|
||||
if (object.hasOwnProperty(key)) {
|
||||
accumulator[key] = object[key];
|
||||
}
|
||||
return accumulator;
|
||||
}, {} as Pick<T, U>);
|
||||
}
|
||||
|
||||
/**
|
||||
* Pick a set of properties from an object using a predicate function
|
||||
*/
|
||||
export function pickBy<T extends object, U extends keyof T>(
|
||||
object: T,
|
||||
predicate: (value: T[U], key: U) => boolean,
|
||||
): Pick<T, U> {
|
||||
return (Object.keys(object) as U[]).reduce((accumulator, key) => {
|
||||
if (predicate(object[key], key)) {
|
||||
accumulator[key] = object[key];
|
||||
}
|
||||
return accumulator;
|
||||
}, {} as Pick<T, U>);
|
||||
}
|
||||
30
frontend/app/src/app/_helpers/collections/shuffle.spec.ts
Normal file
30
frontend/app/src/app/_helpers/collections/shuffle.spec.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* 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 {shuffle} from './shuffle';
|
||||
|
||||
describe('shuffle', function () {
|
||||
it('should shuffle an array', function () {
|
||||
const array = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
|
||||
const shuffled = shuffle(array);
|
||||
expect(shuffled).not.toEqual(array);
|
||||
expect(shuffled).toEqual(jasmine.arrayContaining(array));
|
||||
});
|
||||
|
||||
it('should not modify the original array', function () {
|
||||
const array = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
|
||||
shuffle(array);
|
||||
expect(array).toEqual([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
|
||||
});
|
||||
});
|
||||
28
frontend/app/src/app/_helpers/collections/shuffle.ts
Normal file
28
frontend/app/src/app/_helpers/collections/shuffle.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Shuffles an array
|
||||
*/
|
||||
export function shuffle<T>(array: T[]): T[] {
|
||||
const copy = [...array];
|
||||
const out = [];
|
||||
|
||||
while (copy.length > 0) {
|
||||
out.push(copy.splice(Math.floor(Math.random() * copy.length), 1)[0]);
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* 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 {stringSort, stringSortBy} from './string-sort';
|
||||
|
||||
describe('stringSort', () => {
|
||||
it('should sort an array of strings', () => {
|
||||
expect(['a', 'c', 'b', 'd'].sort(stringSort)).toEqual(['a', 'b', 'c', 'd']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('stringSortBy', () => {
|
||||
it('should sort an array of strings', () => {
|
||||
expect([{item: 'a'}, {item: 'c'}, {item: 'b'}, {item: 'd'}].sort(stringSortBy(it => it.item))).toEqual([
|
||||
{item: 'a'},
|
||||
{item: 'b'},
|
||||
{item: 'c'},
|
||||
{item: 'd'},
|
||||
]);
|
||||
});
|
||||
});
|
||||
36
frontend/app/src/app/_helpers/collections/string-sort.ts
Normal file
36
frontend/app/src/app/_helpers/collections/string-sort.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
/**
|
||||
* sort function for two strings
|
||||
*/
|
||||
export function stringSort(a = '', b = ''): number {
|
||||
if (a < b) return -1;
|
||||
if (a > b) return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* sort function for two strings that allows for a custom transform
|
||||
*/
|
||||
export function stringSortBy<T>(map: (item: T) => string | undefined): (a: T, b: T) => number {
|
||||
return (a: T, b: T): number => {
|
||||
const aValue = map(a) || '';
|
||||
const bValue = map(b) || '';
|
||||
if (aValue < bValue) return -1;
|
||||
if (aValue > bValue) return 1;
|
||||
return 0;
|
||||
};
|
||||
}
|
||||
31
frontend/app/src/app/_helpers/collections/sum.spec.ts
Normal file
31
frontend/app/src/app/_helpers/collections/sum.spec.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* 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 {sum, sumBy} from './sum';
|
||||
|
||||
describe('sum', () => {
|
||||
it('should return the sum of all elements in the collection', () => {
|
||||
const collection = [1, 2, 3, 4, 5];
|
||||
const result = sum(collection);
|
||||
expect(result).toBe(15);
|
||||
});
|
||||
});
|
||||
|
||||
describe('sumBy', function () {
|
||||
it('should return the sum of all elements in the collection', () => {
|
||||
const collection = [{a: 1}, {a: 2}, {a: 3}, {a: 4}, {a: 5}];
|
||||
const result = sumBy(collection, it => it.a);
|
||||
expect(result).toBe(15);
|
||||
});
|
||||
});
|
||||
31
frontend/app/src/app/_helpers/collections/sum.ts
Normal file
31
frontend/app/src/app/_helpers/collections/sum.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Sum an an array
|
||||
*/
|
||||
export function sumBy<T extends object>(
|
||||
collection: T[],
|
||||
transform: (value: T) => number | undefined,
|
||||
): number {
|
||||
return collection.reduce((accumulator, item) => accumulator + (transform(item) || 0), 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sum an array of numbers
|
||||
*/
|
||||
export function sum(collection: Array<number | undefined>): number {
|
||||
return collection.reduce<number>((accumulator, item) => accumulator + (item || 0), 0);
|
||||
}
|
||||
74
frontend/app/src/app/_helpers/collections/tree-group.spec.ts
Normal file
74
frontend/app/src/app/_helpers/collections/tree-group.spec.ts
Normal file
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* 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 {Tree, treeGroupBy} from './tree-group';
|
||||
|
||||
interface TestItem {
|
||||
id: number;
|
||||
path?: string[];
|
||||
}
|
||||
|
||||
describe('tree-group', function () {
|
||||
it('should create a tree', function () {
|
||||
const items: Array<TestItem> = [
|
||||
{
|
||||
id: 1,
|
||||
path: ['a', 'b', 'c'],
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
path: ['a', 'b', 'd'],
|
||||
},
|
||||
];
|
||||
|
||||
const tree = treeGroupBy(items, item => item.path ?? []);
|
||||
|
||||
const expectedTree: Tree<TestItem> = {
|
||||
a: {
|
||||
b: {
|
||||
c: {_: [items[0]]},
|
||||
d: {_: [items[1]]},
|
||||
} as Tree<TestItem>,
|
||||
} as Tree<TestItem>,
|
||||
} as Tree<TestItem>;
|
||||
|
||||
expect(tree).toEqual(expectedTree);
|
||||
});
|
||||
|
||||
it('should also sort empty paths', () => {
|
||||
const items: Array<TestItem> = [
|
||||
{
|
||||
id: 1,
|
||||
path: ['a', 'b', 'c'],
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
},
|
||||
];
|
||||
|
||||
const tree = treeGroupBy(items, item => item.path ?? []);
|
||||
|
||||
const expectedTree: Tree<TestItem> = {
|
||||
a: {
|
||||
b: {
|
||||
c: {_: [items[0]]},
|
||||
} as Tree<TestItem>,
|
||||
} as Tree<TestItem>,
|
||||
_: [items[1]],
|
||||
} as Tree<TestItem>;
|
||||
|
||||
expect(tree).toEqual(expectedTree);
|
||||
});
|
||||
});
|
||||
46
frontend/app/src/app/_helpers/collections/tree-group.ts
Normal file
46
frontend/app/src/app/_helpers/collections/tree-group.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
export type Tree<T> = {
|
||||
[key: string]: Tree<T>;
|
||||
} & {
|
||||
_?: T[] | undefined;
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
export function treeGroupBy<T>(items: T[], transform: (item: T) => string[]): Tree<T> {
|
||||
const tree: Tree<T> = {};
|
||||
|
||||
for (const item of items) {
|
||||
let currentTree = tree;
|
||||
const keys = transform(item);
|
||||
if (keys.length === 0) {
|
||||
currentTree._ = currentTree._ || [];
|
||||
currentTree._.push(item);
|
||||
}
|
||||
for (const [i, key] of keys.entries()) {
|
||||
currentTree = currentTree[key] = (currentTree[key] ?? {}) as Tree<T>;
|
||||
if (i === keys.length - 1) {
|
||||
currentTree._ = currentTree._ ?? [];
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
currentTree._.push(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return tree;
|
||||
}
|
||||
24
frontend/app/src/app/_helpers/collections/uniq.spec.ts
Normal file
24
frontend/app/src/app/_helpers/collections/uniq.spec.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* 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 {uniqBy} from './uniq';
|
||||
|
||||
describe('uniq', function () {
|
||||
it('should return an array with unique values', function () {
|
||||
const array = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
|
||||
const result = uniqBy(array, it => it);
|
||||
expect(result).toEqual([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
|
||||
});
|
||||
});
|
||||
26
frontend/app/src/app/_helpers/collections/uniq.ts
Normal file
26
frontend/app/src/app/_helpers/collections/uniq.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Filter out duplicates from an array.
|
||||
*/
|
||||
export function uniqBy<T>(array: T[], transform: (item: T) => string | number): T[] {
|
||||
return Object.values(
|
||||
array.reduce((accumulator, current) => {
|
||||
accumulator[transform(current)] = current;
|
||||
return accumulator;
|
||||
}, {} as Record<string | number, T>),
|
||||
);
|
||||
}
|
||||
25
frontend/app/src/app/_helpers/collections/zip.spec.ts
Normal file
25
frontend/app/src/app/_helpers/collections/zip.spec.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* 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 {zip} from './zip';
|
||||
|
||||
describe('zip', function () {
|
||||
it('should zip arrays together', function () {
|
||||
expect(zip([1, 2, 3], [4, 5, 6])).toEqual([
|
||||
[1, 4],
|
||||
[2, 5],
|
||||
[3, 6],
|
||||
]);
|
||||
});
|
||||
});
|
||||
21
frontend/app/src/app/_helpers/collections/zip.ts
Normal file
21
frontend/app/src/app/_helpers/collections/zip.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Zip two arrays together.
|
||||
*/
|
||||
export function zip<T, U>(a: T[], b: U[]): [T, U][] {
|
||||
return a.map((_, i) => [a[i], b[i]]);
|
||||
}
|
||||
178
frontend/app/src/app/_helpers/data/filters.ts
Normal file
178
frontend/app/src/app/_helpers/data/filters.ts
Normal file
@@ -0,0 +1,178 @@
|
||||
/*
|
||||
* 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 {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 {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-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;
|
||||
};
|
||||
|
||||
/**
|
||||
* TODO
|
||||
*/
|
||||
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,
|
||||
// eslint-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;
|
||||
}
|
||||
13124
frontend/app/src/app/_helpers/data/resources/test-resources.ts
Normal file
13124
frontend/app/src/app/_helpers/data/resources/test-resources.ts
Normal file
File diff suppressed because it is too large
Load Diff
259
frontend/app/src/app/_helpers/data/sample-configuration.ts
Normal file
259
frontend/app/src/app/_helpers/data/sample-configuration.ts
Normal file
@@ -0,0 +1,259 @@
|
||||
/*
|
||||
* Copyright (C) 2023 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 {
|
||||
SCAboutPageContentType,
|
||||
SCAuthorizationProvider,
|
||||
SCBackendAggregationConfiguration,
|
||||
SCIndexResponse,
|
||||
SCSettingInputType,
|
||||
SCThingOriginType,
|
||||
SCThingType,
|
||||
} from '@openstapps/core';
|
||||
import {Polygon} from 'geojson';
|
||||
import packageJson from '../../../../package.json';
|
||||
|
||||
// provides sample aggregations to be used in tests or backendless development
|
||||
export const sampleAggregations: SCBackendAggregationConfiguration[] = [
|
||||
{
|
||||
fieldName: 'categories',
|
||||
onlyOnTypes: [
|
||||
SCThingType.AcademicEvent,
|
||||
SCThingType.Article,
|
||||
SCThingType.Building,
|
||||
SCThingType.Catalog,
|
||||
SCThingType.Dish,
|
||||
SCThingType.PointOfInterest,
|
||||
SCThingType.Room,
|
||||
],
|
||||
},
|
||||
{
|
||||
fieldName: 'inPlace.name',
|
||||
onlyOnTypes: [
|
||||
SCThingType.DateSeries,
|
||||
SCThingType.Dish,
|
||||
SCThingType.Floor,
|
||||
SCThingType.Organization,
|
||||
SCThingType.PointOfInterest,
|
||||
SCThingType.Room,
|
||||
SCThingType.Ticket,
|
||||
],
|
||||
},
|
||||
{
|
||||
fieldName: 'academicTerms.acronym',
|
||||
onlyOnTypes: [SCThingType.AcademicEvent, SCThingType.SportCourse],
|
||||
},
|
||||
{
|
||||
fieldName: 'academicTerm.acronym',
|
||||
onlyOnTypes: [SCThingType.Catalog],
|
||||
},
|
||||
{
|
||||
fieldName: 'majors',
|
||||
onlyOnTypes: [SCThingType.AcademicEvent],
|
||||
},
|
||||
{
|
||||
fieldName: 'keywords',
|
||||
onlyOnTypes: [SCThingType.Article, SCThingType.Book, SCThingType.Message, SCThingType.Video],
|
||||
},
|
||||
{
|
||||
fieldName: 'type',
|
||||
},
|
||||
];
|
||||
|
||||
export const sampleAuthConfiguration: {
|
||||
default: SCAuthorizationProvider;
|
||||
paia: SCAuthorizationProvider;
|
||||
} = {
|
||||
default: {
|
||||
client: {clientId: '', scopes: '', url: ''},
|
||||
endpoints: {
|
||||
authorization: '',
|
||||
mapping: {id: '', name: ''},
|
||||
token: '',
|
||||
userinfo: '',
|
||||
},
|
||||
},
|
||||
paia: {
|
||||
client: {clientId: '', scopes: '', url: ''},
|
||||
endpoints: {
|
||||
authorization: '',
|
||||
mapping: {id: '', name: ''},
|
||||
token: '',
|
||||
userinfo: '',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const sampleDefaultPolygon: Polygon = {
|
||||
coordinates: [
|
||||
[
|
||||
[8.660432999690723, 50.123027017044436],
|
||||
[8.675496285518358, 50.123027017044436],
|
||||
[8.675496285518358, 50.13066176448642],
|
||||
[8.660432999690723, 50.13066176448642],
|
||||
[8.660432999690723, 50.123027017044436],
|
||||
],
|
||||
],
|
||||
type: 'Polygon',
|
||||
};
|
||||
|
||||
const scVersion = packageJson.dependencies['@openstapps/core'];
|
||||
|
||||
export const sampleIndexResponse: SCIndexResponse = {
|
||||
app: {
|
||||
aboutPages: {
|
||||
about: {
|
||||
title: 'About',
|
||||
content: [
|
||||
{
|
||||
value: 'This is the about page',
|
||||
type: SCAboutPageContentType.MARKDOWN,
|
||||
translations: {
|
||||
en: {
|
||||
value: 'This is the about page',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
translations: {
|
||||
en: {
|
||||
title: 'About',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
campusPolygon: {
|
||||
coordinates: [[[1, 2]], [[1, 2]]],
|
||||
type: 'Polygon',
|
||||
},
|
||||
features: {},
|
||||
menus: [
|
||||
{
|
||||
icon: 'icon',
|
||||
items: [
|
||||
{
|
||||
icon: 'icon',
|
||||
route: '/index',
|
||||
title: 'start',
|
||||
translations: {
|
||||
de: {
|
||||
title: 'Start',
|
||||
},
|
||||
en: {
|
||||
title: 'start',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
title: 'main',
|
||||
route: '/main',
|
||||
translations: {
|
||||
de: {
|
||||
title: 'Haupt',
|
||||
},
|
||||
en: {
|
||||
title: 'main',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
name: 'StApps',
|
||||
privacyPolicyUrl: 'foo.bar',
|
||||
settings: [
|
||||
{
|
||||
categories: ['credentials'],
|
||||
defaultValue: '',
|
||||
inputType: SCSettingInputType.Text,
|
||||
name: 'username',
|
||||
order: 0,
|
||||
origin: {
|
||||
indexed: '2018-09-11T12:30:00Z',
|
||||
name: 'Dummy',
|
||||
type: SCThingOriginType.Remote,
|
||||
},
|
||||
translations: {
|
||||
de: {
|
||||
name: 'Benutzername',
|
||||
},
|
||||
en: {
|
||||
name: 'Username',
|
||||
},
|
||||
},
|
||||
type: SCThingType.Setting,
|
||||
uid: '',
|
||||
},
|
||||
],
|
||||
},
|
||||
auth: {},
|
||||
backend: {
|
||||
SCVersion: scVersion,
|
||||
externalRequestTimeout: 5000,
|
||||
hiddenTypes: [SCThingType.DateSeries, SCThingType.Diff, SCThingType.Floor],
|
||||
mappingIgnoredTags: [],
|
||||
maxMultiSearchRouteQueries: 5,
|
||||
maxRequestBodySize: 512 * 1024,
|
||||
name: 'Technische Universität Berlin',
|
||||
namespace: '909a8cbc-8520-456c-b474-ef1525f14209',
|
||||
sortableFields: [
|
||||
{
|
||||
fieldName: 'name',
|
||||
sortTypes: ['ducet'],
|
||||
},
|
||||
{
|
||||
fieldName: 'type',
|
||||
sortTypes: ['ducet'],
|
||||
},
|
||||
{
|
||||
fieldName: 'categories',
|
||||
onlyOnTypes: [
|
||||
SCThingType.AcademicEvent,
|
||||
SCThingType.Building,
|
||||
SCThingType.Catalog,
|
||||
SCThingType.Dish,
|
||||
SCThingType.PointOfInterest,
|
||||
SCThingType.Room,
|
||||
],
|
||||
sortTypes: ['ducet'],
|
||||
},
|
||||
{
|
||||
fieldName: 'geo',
|
||||
onlyOnTypes: [SCThingType.Building, SCThingType.PointOfInterest, SCThingType.Room],
|
||||
sortTypes: ['distance'],
|
||||
},
|
||||
{
|
||||
fieldName: 'geo',
|
||||
onlyOnTypes: [SCThingType.Building, SCThingType.PointOfInterest, SCThingType.Room],
|
||||
sortTypes: ['distance'],
|
||||
},
|
||||
{
|
||||
fieldName: 'inPlace.geo',
|
||||
onlyOnTypes: [
|
||||
SCThingType.DateSeries,
|
||||
SCThingType.Dish,
|
||||
SCThingType.Floor,
|
||||
SCThingType.Organization,
|
||||
SCThingType.PointOfInterest,
|
||||
SCThingType.Room,
|
||||
SCThingType.Ticket,
|
||||
],
|
||||
sortTypes: ['distance'],
|
||||
},
|
||||
{
|
||||
fieldName: 'offers',
|
||||
onlyOnTypes: [SCThingType.Dish],
|
||||
sortTypes: ['price'],
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
113
frontend/app/src/app/_helpers/data/sample-facets.ts
Normal file
113
frontend/app/src/app/_helpers/data/sample-facets.ts
Normal file
@@ -0,0 +1,113 @@
|
||||
/*
|
||||
* Copyright (C) 2019, 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 {SCFacet, SCThingType} from '@openstapps/core';
|
||||
|
||||
export const facetsMock: SCFacet[] = [
|
||||
{
|
||||
buckets: [
|
||||
{
|
||||
count: 60,
|
||||
key: 'academic event',
|
||||
},
|
||||
{
|
||||
count: 160,
|
||||
key: 'message',
|
||||
},
|
||||
{
|
||||
count: 151,
|
||||
key: 'date series',
|
||||
},
|
||||
{
|
||||
count: 106,
|
||||
key: 'dish',
|
||||
},
|
||||
{
|
||||
count: 20,
|
||||
key: 'building',
|
||||
},
|
||||
{
|
||||
count: 20,
|
||||
key: 'semester',
|
||||
},
|
||||
],
|
||||
field: 'type',
|
||||
},
|
||||
{
|
||||
buckets: [
|
||||
{
|
||||
count: 12,
|
||||
key: 'Max Mustermann',
|
||||
},
|
||||
{
|
||||
count: 2,
|
||||
key: 'Foo Bar',
|
||||
},
|
||||
],
|
||||
field: 'performers',
|
||||
onlyOnType: SCThingType.AcademicEvent,
|
||||
},
|
||||
{
|
||||
buckets: [
|
||||
{
|
||||
count: 5,
|
||||
key: 'colloquium',
|
||||
},
|
||||
{
|
||||
count: 15,
|
||||
key: 'course',
|
||||
},
|
||||
],
|
||||
field: 'categories',
|
||||
onlyOnType: SCThingType.AcademicEvent,
|
||||
},
|
||||
{
|
||||
buckets: [
|
||||
{
|
||||
count: 5,
|
||||
key: 'unipedia',
|
||||
},
|
||||
],
|
||||
field: 'categories',
|
||||
onlyOnType: SCThingType.Article,
|
||||
},
|
||||
{
|
||||
buckets: [
|
||||
{
|
||||
count: 5,
|
||||
key: 'employees',
|
||||
},
|
||||
{
|
||||
count: 15,
|
||||
key: 'students',
|
||||
},
|
||||
],
|
||||
field: 'audiences',
|
||||
onlyOnType: SCThingType.Message,
|
||||
},
|
||||
{
|
||||
buckets: [
|
||||
{
|
||||
count: 5,
|
||||
key: 'main dish',
|
||||
},
|
||||
{
|
||||
count: 15,
|
||||
key: 'salad',
|
||||
},
|
||||
],
|
||||
field: 'categories',
|
||||
onlyOnType: SCThingType.Dish,
|
||||
},
|
||||
];
|
||||
438
frontend/app/src/app/_helpers/data/sample-things.ts
Normal file
438
frontend/app/src/app/_helpers/data/sample-things.ts
Normal file
@@ -0,0 +1,438 @@
|
||||
/*
|
||||
* 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 {HttpClient} from '@angular/common/http';
|
||||
import {Injectable} from '@angular/core';
|
||||
import {
|
||||
SCAcademicEvent,
|
||||
SCArticle,
|
||||
SCBook,
|
||||
SCBuilding,
|
||||
SCCatalog,
|
||||
SCDateSeries,
|
||||
SCDish,
|
||||
SCFavorite,
|
||||
SCMessage,
|
||||
SCPerson,
|
||||
SCRoom,
|
||||
SCSearchFilter,
|
||||
SCThing,
|
||||
SCThingOriginType,
|
||||
SCThingType,
|
||||
SCToDo,
|
||||
SCToDoPriority,
|
||||
} from '@openstapps/core';
|
||||
import {Observable, of} from 'rxjs';
|
||||
import {checkFilter} from './filters';
|
||||
import {sampleResources} from './resources/test-resources';
|
||||
|
||||
/* eslint-disable */
|
||||
const sampleMessages: SCMessage[] = [
|
||||
{
|
||||
audiences: ['students'],
|
||||
categories: ['news'],
|
||||
messageBody: 'Foo Message Text',
|
||||
name: 'Foo Message',
|
||||
origin: {
|
||||
indexed: 'SOME-DATE',
|
||||
name: 'some name',
|
||||
type: SCThingOriginType.Remote,
|
||||
},
|
||||
type: SCThingType.Message,
|
||||
uid: 'message-123',
|
||||
},
|
||||
{
|
||||
audiences: ['employees'],
|
||||
categories: ['news'],
|
||||
messageBody: 'Bar Message Text',
|
||||
name: 'Bar Message',
|
||||
origin: {
|
||||
indexed: 'SOME-DATE',
|
||||
name: 'some name',
|
||||
type: SCThingOriginType.Remote,
|
||||
},
|
||||
type: SCThingType.Message,
|
||||
uid: 'message-456',
|
||||
},
|
||||
];
|
||||
|
||||
const sampleDishes: SCDish[] = [
|
||||
{
|
||||
categories: ['main dish', 'salad'],
|
||||
name: 'Foo Dish',
|
||||
// offers: [
|
||||
// {
|
||||
// 'availability': 'in stock',
|
||||
// 'availabilityStarts': '2017-01-30T00:00:00.000Z',
|
||||
// 'availabilityEnds': '2017-01-30T23:59:59.999Z',
|
||||
// 'prices': {
|
||||
// 'default': 4.85,
|
||||
// 'student': 2.85,
|
||||
// 'employee': 3.85,
|
||||
// 'guest': 4.85,
|
||||
// },
|
||||
// ],
|
||||
origin: {
|
||||
indexed: 'SOME-DATE',
|
||||
name: 'some name',
|
||||
type: SCThingOriginType.Remote,
|
||||
},
|
||||
type: SCThingType.Dish,
|
||||
uid: 'dish-123',
|
||||
},
|
||||
{
|
||||
categories: ['side dish', 'salad'],
|
||||
name: 'Bar Dish',
|
||||
origin: {
|
||||
indexed: 'SOME-DATE',
|
||||
name: 'some name',
|
||||
type: SCThingOriginType.Remote,
|
||||
},
|
||||
type: SCThingType.Dish,
|
||||
uid: 'dish-456',
|
||||
},
|
||||
];
|
||||
|
||||
const sampleBuildings: SCBuilding[] = [
|
||||
{
|
||||
categories: ['education'],
|
||||
geo: {
|
||||
point: {type: 'Point', coordinates: [12, 12]},
|
||||
},
|
||||
name: 'Foo Building',
|
||||
origin: {
|
||||
indexed: 'SOME-DATE',
|
||||
name: 'some name',
|
||||
type: SCThingOriginType.Remote,
|
||||
},
|
||||
type: SCThingType.Building,
|
||||
uid: 'building-123',
|
||||
},
|
||||
];
|
||||
|
||||
const sampleRooms: SCRoom[] = [
|
||||
{
|
||||
categories: ['library'],
|
||||
geo: {
|
||||
point: {type: 'Point', coordinates: [12, 12]},
|
||||
},
|
||||
name: 'Foo Room',
|
||||
origin: {
|
||||
indexed: 'SOME-DATE',
|
||||
name: 'some name',
|
||||
type: SCThingOriginType.Remote,
|
||||
},
|
||||
type: SCThingType.Room,
|
||||
uid: 'room-123',
|
||||
},
|
||||
];
|
||||
|
||||
const sampleArticles: SCArticle[] = [
|
||||
{
|
||||
articleBody: 'Foo Text',
|
||||
categories: ['unipedia'],
|
||||
name: 'Foo Article',
|
||||
origin: {
|
||||
indexed: 'SOME-DATE',
|
||||
name: 'some name',
|
||||
type: SCThingOriginType.Remote,
|
||||
},
|
||||
type: SCThingType.Article,
|
||||
uid: 'article-123',
|
||||
},
|
||||
{
|
||||
articleBody: 'Bar Text',
|
||||
categories: ['unipedia'],
|
||||
name: 'Bar Article',
|
||||
origin: {
|
||||
indexed: 'SOME-DATE',
|
||||
name: 'some name',
|
||||
type: SCThingOriginType.Remote,
|
||||
},
|
||||
type: SCThingType.Article,
|
||||
uid: 'article-456',
|
||||
},
|
||||
];
|
||||
|
||||
const samplePersons: SCPerson[] = [
|
||||
{
|
||||
familyName: 'Person',
|
||||
givenName: 'Foo',
|
||||
name: 'Foo Person',
|
||||
origin: {
|
||||
indexed: 'SOME-DATE',
|
||||
name: 'some name',
|
||||
type: SCThingOriginType.Remote,
|
||||
},
|
||||
type: SCThingType.Person,
|
||||
uid: 'person-123',
|
||||
},
|
||||
{
|
||||
familyName: 'Person',
|
||||
givenName: 'Bar',
|
||||
name: 'Bar Person',
|
||||
origin: {
|
||||
indexed: 'SOME-DATE',
|
||||
name: 'some name',
|
||||
type: SCThingOriginType.Remote,
|
||||
},
|
||||
type: SCThingType.Person,
|
||||
uid: 'person-456',
|
||||
},
|
||||
];
|
||||
|
||||
const sampleBooks: SCBook[] = [
|
||||
{
|
||||
authors: samplePersons,
|
||||
ISBNs: ['123456'],
|
||||
categories: ['ebook'],
|
||||
name: 'Foo Book',
|
||||
origin: {
|
||||
indexed: 'SOME-DATE',
|
||||
name: 'some name',
|
||||
type: SCThingOriginType.Remote,
|
||||
},
|
||||
type: SCThingType.Book,
|
||||
uid: 'HEB290615194',
|
||||
},
|
||||
{
|
||||
authors: [],
|
||||
ISBNs: ['123456'],
|
||||
categories: ['book'],
|
||||
name: 'Bar Book',
|
||||
origin: {
|
||||
indexed: 'SOME-DATE',
|
||||
name: 'some name',
|
||||
type: SCThingOriginType.Remote,
|
||||
},
|
||||
type: SCThingType.Book,
|
||||
uid: 'book-234',
|
||||
},
|
||||
];
|
||||
|
||||
const sampleCatalogs: SCCatalog[] = [
|
||||
{
|
||||
categories: ['university events'],
|
||||
level: 1,
|
||||
name: 'Foo Catalog',
|
||||
origin: {
|
||||
indexed: 'SOME-DATE',
|
||||
name: 'some name',
|
||||
type: SCThingOriginType.Remote,
|
||||
},
|
||||
type: SCThingType.Catalog,
|
||||
uid: 'catalog-123',
|
||||
},
|
||||
{
|
||||
categories: ['university events'],
|
||||
level: 1,
|
||||
name: 'Bar Catalog',
|
||||
origin: {
|
||||
indexed: 'SOME-DATE',
|
||||
name: 'some name',
|
||||
type: SCThingOriginType.Remote,
|
||||
},
|
||||
type: SCThingType.Catalog,
|
||||
uid: 'catalog-456',
|
||||
},
|
||||
];
|
||||
|
||||
const sampleTodos: SCToDo[] = [
|
||||
{
|
||||
categories: ['foo category'],
|
||||
done: false,
|
||||
name: 'Foo Todo',
|
||||
origin: {
|
||||
indexed: 'SOME-DATE',
|
||||
name: 'some name',
|
||||
type: SCThingOriginType.Remote,
|
||||
},
|
||||
priority: SCToDoPriority.LOW,
|
||||
type: SCThingType.ToDo,
|
||||
uid: 'todo-123',
|
||||
},
|
||||
{
|
||||
categories: ['bar category'],
|
||||
done: true,
|
||||
name: 'Bar Todo',
|
||||
origin: {
|
||||
indexed: 'SOME-DATE',
|
||||
name: 'some name',
|
||||
type: SCThingOriginType.Remote,
|
||||
},
|
||||
priority: SCToDoPriority.HIGH,
|
||||
type: SCThingType.ToDo,
|
||||
uid: 'todo-456',
|
||||
},
|
||||
];
|
||||
|
||||
const sampleFavorites: SCFavorite[] = [
|
||||
{
|
||||
data: sampleBuildings[0],
|
||||
name: 'Foo Favorite',
|
||||
origin: {
|
||||
created: 'SOME-DATE',
|
||||
type: SCThingOriginType.User,
|
||||
},
|
||||
type: SCThingType.Favorite,
|
||||
uid: 'favorite-123',
|
||||
},
|
||||
{
|
||||
data: samplePersons[1],
|
||||
name: 'Bar Favorite',
|
||||
origin: {
|
||||
created: 'SOME-DATE',
|
||||
type: SCThingOriginType.User,
|
||||
},
|
||||
type: SCThingType.Favorite,
|
||||
uid: 'favorite-456',
|
||||
},
|
||||
];
|
||||
|
||||
const sampleAcademicEvents: SCAcademicEvent[] = [
|
||||
{
|
||||
categories: ['course'],
|
||||
majors: ['Major One', 'Major Two'],
|
||||
name: 'Foo Academic Event',
|
||||
origin: {
|
||||
indexed: 'SOME-DATE',
|
||||
name: 'some name',
|
||||
type: SCThingOriginType.Remote,
|
||||
},
|
||||
performers: samplePersons,
|
||||
type: SCThingType.AcademicEvent,
|
||||
uid: 'academic-event-123',
|
||||
},
|
||||
{
|
||||
categories: ['practicum'],
|
||||
majors: ['Major Two', 'Major Three'],
|
||||
name: 'Bar Academic Event',
|
||||
origin: {
|
||||
indexed: 'SOME-DATE',
|
||||
name: 'some name',
|
||||
type: SCThingOriginType.Remote,
|
||||
},
|
||||
performers: samplePersons,
|
||||
type: SCThingType.AcademicEvent,
|
||||
uid: 'academic-event-456',
|
||||
},
|
||||
];
|
||||
|
||||
const sampleDateSeries: SCDateSeries[] = [
|
||||
{
|
||||
dates: ['2019-03-01T17:00:00+00:00', '2019-03-08T17:00:00+00:00'],
|
||||
duration: 'PT2H',
|
||||
event: sampleAcademicEvents[0],
|
||||
repeatFrequency: 'P1W',
|
||||
name: 'Foo Date Event - Date Series',
|
||||
origin: {
|
||||
indexed: 'SOME-DATE',
|
||||
name: 'some name',
|
||||
type: SCThingOriginType.Remote,
|
||||
},
|
||||
type: SCThingType.DateSeries,
|
||||
uid: 'date-series-123',
|
||||
},
|
||||
{
|
||||
dates: ['2019-03-03T10:00:00+00:00', '2019-03-11T10:00:00+00:00'],
|
||||
duration: 'PT2H',
|
||||
event: sampleAcademicEvents[1],
|
||||
name: 'Bar Date Event - Date Series',
|
||||
origin: {
|
||||
indexed: 'SOME-DATE',
|
||||
name: 'some name',
|
||||
type: SCThingOriginType.Remote,
|
||||
},
|
||||
type: SCThingType.DateSeries,
|
||||
uid: 'date-series-456',
|
||||
},
|
||||
];
|
||||
|
||||
export const sampleThingsMap: {[key in SCThingType | string]: SCThing[]} = {
|
||||
'academic event': sampleAcademicEvents,
|
||||
'article': sampleArticles,
|
||||
'book': sampleBooks,
|
||||
'building': sampleBuildings,
|
||||
'catalog': sampleCatalogs,
|
||||
'course of studies': [],
|
||||
'date series': sampleDateSeries,
|
||||
'diff': [],
|
||||
'dish': sampleDishes,
|
||||
'favorite': sampleFavorites,
|
||||
'floor': [],
|
||||
'message': sampleMessages,
|
||||
'organization': [],
|
||||
'person': samplePersons,
|
||||
'point of interest': [],
|
||||
'room': sampleRooms,
|
||||
'semester': [],
|
||||
'setting': [],
|
||||
'sport course': [],
|
||||
'ticket': [],
|
||||
'todo': sampleTodos,
|
||||
'tour': [],
|
||||
'video': [],
|
||||
};
|
||||
|
||||
/**
|
||||
* TODO
|
||||
*/
|
||||
@Injectable()
|
||||
export class SampleThings {
|
||||
/**
|
||||
* TODO
|
||||
*/
|
||||
http: HttpClient;
|
||||
|
||||
constructor(http: HttpClient) {
|
||||
this.http = http;
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO
|
||||
*/
|
||||
// eslint-disable-next-line class-methods-use-this, @typescript-eslint/no-explicit-any
|
||||
getSampleThing(uid: string): Observable<any[]> {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const sampleThings: any[] = [];
|
||||
for (const resource of sampleResources) {
|
||||
if ((resource.instance.uid as SCThingType) === uid) {
|
||||
sampleThings.push(resource.instance);
|
||||
|
||||
return of(sampleThings);
|
||||
}
|
||||
}
|
||||
|
||||
return of(sampleThings);
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO
|
||||
*/
|
||||
// eslint-disable-next-line class-methods-use-this, @typescript-eslint/no-explicit-any
|
||||
getSampleThings(filter?: SCSearchFilter): Observable<any[]> {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const sampleThings: any[] = [];
|
||||
for (const resource of sampleResources) {
|
||||
// eslint-disable-next-line max-len
|
||||
// if ([SCThingType.Video].includes(resource.instance.type as SCThingType)) {
|
||||
if (typeof filter === 'undefined' || checkFilter(resource.instance as SCThing, filter)) {
|
||||
sampleThings.push(resource.instance);
|
||||
}
|
||||
// }
|
||||
}
|
||||
|
||||
return of(sampleThings);
|
||||
}
|
||||
}
|
||||
30
frontend/app/src/app/_helpers/errors.ts
Normal file
30
frontend/app/src/app/_helpers/errors.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
/**
|
||||
* An error that can occur in the StApps app
|
||||
*/
|
||||
export class AppError extends Error {
|
||||
/**
|
||||
* TODO
|
||||
*
|
||||
* @param name Name of the error
|
||||
* @param message Message of the error
|
||||
*/
|
||||
constructor(name: string, message: string) {
|
||||
super(message);
|
||||
this.name = name;
|
||||
}
|
||||
}
|
||||
34
frontend/app/src/app/_helpers/rxjs/mutation-observer.ts
Normal file
34
frontend/app/src/app/_helpers/rxjs/mutation-observer.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright (C) 2023 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 {Observable} from 'rxjs';
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
export function fromMutationObserver(
|
||||
target: Node,
|
||||
options?: MutationObserverInit,
|
||||
): Observable<MutationRecord[]> {
|
||||
return new Observable(subscriber => {
|
||||
const observer = new MutationObserver(mutations => {
|
||||
subscriber.next(mutations);
|
||||
});
|
||||
observer.observe(target, options);
|
||||
return () => {
|
||||
observer.disconnect();
|
||||
};
|
||||
});
|
||||
}
|
||||
42
frontend/app/src/app/_helpers/service-handler.interceptor.ts
Normal file
42
frontend/app/src/app/_helpers/service-handler.interceptor.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* 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 {HttpRequest, HttpHandler, HttpEvent, HttpInterceptor, HttpErrorResponse} from '@angular/common/http';
|
||||
import {Observable, throwError} from 'rxjs';
|
||||
import {NGXLogger} from 'ngx-logger';
|
||||
import {catchError} from 'rxjs/operators';
|
||||
|
||||
@Injectable()
|
||||
export class ServiceHandlerInterceptor implements HttpInterceptor {
|
||||
constructor(private readonly logger: NGXLogger) {}
|
||||
|
||||
intercept(request: HttpRequest<unknown>, next: HttpHandler): Observable<HttpEvent<unknown>> {
|
||||
return next.handle(request).pipe(
|
||||
// Fixes the issue of errors dropping into "toPromise()"
|
||||
// and being not able to catch it in the "caller methods"
|
||||
catchError((error: HttpErrorResponse) => {
|
||||
const errorMessage =
|
||||
error.error instanceof ErrorEvent
|
||||
? `Error: ${error.error.message}`
|
||||
: `Error Code: ${error.status}, Message: ${error.message}`;
|
||||
|
||||
this.logger.error(errorMessage);
|
||||
|
||||
return throwError(error);
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
19
frontend/app/src/app/_helpers/ts-logger.ts
Normal file
19
frontend/app/src/app/_helpers/ts-logger.ts
Normal file
@@ -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);
|
||||
90
frontend/app/src/app/animation/animation-choreographer.ts
Normal file
90
frontend/app/src/app/animation/animation-choreographer.ts
Normal file
@@ -0,0 +1,90 @@
|
||||
/*
|
||||
* 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 {SHARED_AXIS_DIRECTIONS} from './material-motion';
|
||||
|
||||
/**
|
||||
* /**
|
||||
* Choreograph a shared axis animation based on a row of values so that changing state
|
||||
* results in the correct, expected behavior of reverting the previous animation etc.
|
||||
*
|
||||
* The Choreographer manages motion of an element that changes value. This can be used in a variety of ways,
|
||||
* for example multi-view choreographing can be achieved as such
|
||||
*
|
||||
* ```html
|
||||
* <div [ngSwitch]='choreographer.state'
|
||||
* [@animation]='choreographer.animationState'
|
||||
* [@animation.done]='choreographer.done()'>
|
||||
* <div *ngSwitchCase='"a"'/>
|
||||
* <div *ngSwitchCase='"b"'/>
|
||||
* </div>
|
||||
* ```
|
||||
*
|
||||
* @see {@link https://material.io/design/motion/the-motion-system.html#shared-axis}
|
||||
*/
|
||||
export class SharedAxisChoreographer<T> {
|
||||
/**
|
||||
* Expected next value
|
||||
*/
|
||||
private expectedValue: T;
|
||||
|
||||
/**
|
||||
* Animation State
|
||||
*/
|
||||
animationState: string;
|
||||
|
||||
/**
|
||||
* Current value to read from
|
||||
*/
|
||||
currentValue: T;
|
||||
|
||||
constructor(initialValue: T, readonly pages?: T[]) {
|
||||
this.currentValue = initialValue;
|
||||
this.expectedValue = initialValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Must be linked to the animation callback
|
||||
*/
|
||||
animationDone() {
|
||||
this.animationState = 'in';
|
||||
this.currentValue = this.expectedValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Change view for a new state that the current active view should receive
|
||||
*/
|
||||
changeViewForState(newValue: T, direction?: -1 | 0 | 1) {
|
||||
if (direction === 0) {
|
||||
this.currentValue = this.expectedValue = newValue;
|
||||
return;
|
||||
}
|
||||
|
||||
this.expectedValue = newValue;
|
||||
|
||||
// pre-place animation state
|
||||
// new element comes in from the right and pushes the old one to the left
|
||||
this.animationState = SHARED_AXIS_DIRECTIONS[direction ?? this.getDirection(this.currentValue, newValue)];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get direction from to
|
||||
*/
|
||||
getDirection(from: T, to: T) {
|
||||
const element = this.pages?.find(it => it === from || it === to);
|
||||
|
||||
return element === from ? 1 : element === to ? -1 : 0;
|
||||
}
|
||||
}
|
||||
20
frontend/app/src/app/animation/easings.ts
Normal file
20
frontend/app/src/app/animation/easings.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* Copyright (C) 2023 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/>.
|
||||
*/
|
||||
|
||||
// these are the ionic values
|
||||
export const iosEasing = 'cubic-bezier(0.32,0.72,0,1)';
|
||||
export const iosDuration = 540;
|
||||
export const mdEasing = 'cubic-bezier(0.36,0.66,0.04,1)';
|
||||
export const mdDuration = 280;
|
||||
83
frontend/app/src/app/animation/fab-expand.ts
Normal file
83
frontend/app/src/app/animation/fab-expand.ts
Normal file
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* Copyright (C) 2023 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 {AnimationBuilder, AnimationController} from '@ionic/angular';
|
||||
import {AnimationOptions} from '@ionic/angular/providers/nav-controller';
|
||||
import {iosDuration, iosEasing, mdDuration, mdEasing} from './easings';
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
export function fabExpand(animationController: AnimationController): AnimationBuilder {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
return (_baseElement: HTMLElement, options: AnimationOptions | any) => {
|
||||
const rootTransition = animationController
|
||||
.create()
|
||||
.duration(options.duration ?? (options.mode === 'ios' ? iosDuration : mdDuration * 1.4))
|
||||
.easing(options.mode === 'ios' ? iosEasing : mdEasing);
|
||||
const back = options.direction === 'back';
|
||||
const fabView = back ? options.enteringEl! : options.leavingEl!;
|
||||
const otherView = back ? options.leavingEl! : options.enteringEl!;
|
||||
|
||||
const fab = fabView.querySelector('ion-fab-button').shadowRoot.querySelector('.button-native');
|
||||
const fabBounds = fab.getBoundingClientRect();
|
||||
const viewBounds = otherView.getBoundingClientRect();
|
||||
|
||||
const useReducedMotion = viewBounds.width > 500;
|
||||
const reducedMotionTransform = `${Math.min(viewBounds.width * 0.3, 200)}px`;
|
||||
const reducedMotionViewBorderRadius = '128px';
|
||||
const reducedMotionFabGrow = '2';
|
||||
const reducedMotionViewShrink = '0.9';
|
||||
|
||||
const viewCenterX = (viewBounds.width - viewBounds.x) / 2;
|
||||
const viewCenterY = (viewBounds.height - viewBounds.y) / 2;
|
||||
|
||||
const viewOnFab = useReducedMotion
|
||||
? `translate(${reducedMotionTransform}, ${reducedMotionTransform}) scale(${reducedMotionViewShrink})`
|
||||
: `translate(${(fabBounds.x - viewBounds.x) / 2}px, ${(fabBounds.y - viewBounds.y) / 2}px) scale(${
|
||||
fabBounds.width / viewBounds.width
|
||||
}, ${fabBounds.height / viewBounds.height})`;
|
||||
const fabOnView = useReducedMotion
|
||||
? `translate(-${reducedMotionTransform}, -${reducedMotionTransform}) scale(${reducedMotionFabGrow})`
|
||||
: `translate(${viewCenterX - fabBounds.x}px, ${viewCenterY - fabBounds.y}px) scale(${
|
||||
viewBounds.width / fabBounds.width
|
||||
}, ${viewBounds.height / fabBounds.height})`;
|
||||
const transformNormal = `translate(0px, 0px) scale(1, 1)`;
|
||||
|
||||
const viewBorderRadius = useReducedMotion ? reducedMotionViewBorderRadius : '50%';
|
||||
|
||||
const fabViewFade = animationController
|
||||
.create()
|
||||
.beforeStyles({zIndex: -1})
|
||||
.fromTo('opacity', '1', '1')
|
||||
.addElement(fabView);
|
||||
const fabGrow = animationController
|
||||
.create()
|
||||
.beforeStyles({transformOrigin: 'center'})
|
||||
.fromTo('transform', back ? fabOnView : transformNormal, back ? transformNormal : fabOnView)
|
||||
.fromTo('opacity', back ? '0' : '1', back ? '1' : '0')
|
||||
.fromTo('borderRadius', back ? '0' : '50%', back ? '50%' : '0')
|
||||
.addElement(fab);
|
||||
const viewGrow = animationController
|
||||
.create()
|
||||
.beforeStyles({zIndex: 200, overflow: 'hidden', transformOrigin: 'center'})
|
||||
.fromTo('transform', back ? transformNormal : viewOnFab, back ? viewOnFab : transformNormal)
|
||||
.fromTo('opacity', back ? '1' : '0', back ? '0' : '1')
|
||||
.fromTo('borderRadius', back ? '0' : viewBorderRadius, back ? viewBorderRadius : '0')
|
||||
.addElement(otherView);
|
||||
|
||||
return rootTransition.addAnimation(fabGrow).addAnimation(viewGrow).addAnimation(fabViewFade);
|
||||
};
|
||||
}
|
||||
78
frontend/app/src/app/animation/material-motion.ts
Normal file
78
frontend/app/src/app/animation/material-motion.ts
Normal file
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
* 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 {animate, sequence, state, style, transition, trigger} from '@angular/animations';
|
||||
|
||||
/**
|
||||
* Fade transition
|
||||
*
|
||||
* @see {@link https://material.io/design/motion/the-motion-system.html#fade}
|
||||
*/
|
||||
export const materialFade = trigger('materialFade', [
|
||||
state('in', style({opacity: 1})),
|
||||
transition(':enter', [style({opacity: 0}), animate('250ms ease')]),
|
||||
transition(':leave', [animate('200ms ease', style({opacity: 0}))]),
|
||||
]);
|
||||
|
||||
/**
|
||||
* Fade transition
|
||||
*
|
||||
* @see {@link https://material.io/design/motion/the-motion-system.html#fade}
|
||||
*/
|
||||
export const materialManualFade = trigger('materialManualFade', [
|
||||
state('in', style({opacity: 1})),
|
||||
state('out', style({opacity: 0})),
|
||||
transition('in => out', animate('200ms ease')),
|
||||
transition('out => in', animate('250ms ease')),
|
||||
]);
|
||||
|
||||
/**
|
||||
* Fade through transition
|
||||
*
|
||||
* @see {@link https://material.io/design/motion/the-motion-system.html#fade-through}
|
||||
*/
|
||||
export const materialFadeThrough = trigger('materialFadeThrough', [
|
||||
state('in', style({transform: 'scale(100%)', opacity: 1})),
|
||||
transition(':enter', [style({transform: 'scale(80%)', opacity: 0}), animate('250ms ease')]),
|
||||
transition(':leave', [animate('200ms ease', style({opacity: 0}))]),
|
||||
]);
|
||||
|
||||
export const SHARED_AXIS_DIRECTIONS = {
|
||||
[-1]: 'go-backward',
|
||||
[0]: 'in',
|
||||
[1]: 'go-forward',
|
||||
};
|
||||
|
||||
/**
|
||||
* Shared axis transition along the X-Axis
|
||||
*
|
||||
* Needs to be manually choreographed
|
||||
*
|
||||
* @see {@link https://material.io/design/motion/the-motion-system.html#shared-axis}
|
||||
* @see {SharedAxisChoreographer}
|
||||
*/
|
||||
export const materialSharedAxisX = trigger('materialSharedAxisX', [
|
||||
state(SHARED_AXIS_DIRECTIONS[-1], style({opacity: 0, transform: 'translateX(30px)'})),
|
||||
state(SHARED_AXIS_DIRECTIONS[0], style({opacity: 1, transform: 'translateX(0px)'})),
|
||||
state(SHARED_AXIS_DIRECTIONS[1], style({opacity: 0, transform: 'translateX(-30px)'})),
|
||||
transition(
|
||||
`${SHARED_AXIS_DIRECTIONS[-1]} => ${SHARED_AXIS_DIRECTIONS[0]}`,
|
||||
sequence([style({opacity: 0, transform: 'translateX(-30px)'}), animate('100ms ease-out')]),
|
||||
),
|
||||
transition(`${SHARED_AXIS_DIRECTIONS[0]} => *`, animate('100ms ease-out')),
|
||||
transition(
|
||||
`${SHARED_AXIS_DIRECTIONS[1]} => ${SHARED_AXIS_DIRECTIONS[0]}`,
|
||||
sequence([style({opacity: 0, transform: 'translateX(30px)'}), animate('100ms ease-out')]),
|
||||
),
|
||||
]);
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Copyright (C) 2021 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 {animate, style, transition, trigger} from '@angular/animations';
|
||||
|
||||
export const chipTransition = trigger('chipTransition', [
|
||||
transition(':enter', [
|
||||
style({
|
||||
'opacity': 0,
|
||||
'transform': 'scaleX(80%)',
|
||||
'transform-origin': 'left',
|
||||
}),
|
||||
animate('200ms ease', style({opacity: 1, transform: 'scaleX(100%)'})),
|
||||
]),
|
||||
]);
|
||||
|
||||
export const chipSkeletonTransition = trigger('chipSkeletonTransition', [
|
||||
transition(':leave', [
|
||||
style({
|
||||
'opacity': 1,
|
||||
'transform': 'scaleX(100%)',
|
||||
'transform-origin': 'left',
|
||||
}),
|
||||
animate('200ms ease', style({opacity: 0, transform: 'scaleX(120%)'})),
|
||||
]),
|
||||
]);
|
||||
37
frontend/app/src/app/app-routing.module.ts
Normal file
37
frontend/app/src/app/app-routing.module.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Copyright (C) 2018, 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 {PreloadAllModules, RouterModule, Routes} from '@angular/router';
|
||||
|
||||
const routes: Routes = [{path: '', redirectTo: '/overview', pathMatch: 'full'}];
|
||||
|
||||
/**
|
||||
* TODO
|
||||
*/
|
||||
@NgModule({
|
||||
exports: [RouterModule],
|
||||
imports: [
|
||||
RouterModule.forRoot(routes, {
|
||||
preloadingStrategy: PreloadAllModules,
|
||||
errorHandler: error => {
|
||||
// Handle unknown routes, at the moment this can only be done via window.location
|
||||
if (error.message.includes('Cannot match any routes')) {
|
||||
window.location.href = '/overview';
|
||||
}
|
||||
},
|
||||
}),
|
||||
],
|
||||
})
|
||||
export class AppRoutingModule {}
|
||||
18
frontend/app/src/app/app.component.html
Normal file
18
frontend/app/src/app/app.component.html
Normal file
@@ -0,0 +1,18 @@
|
||||
<!--
|
||||
~ Copyright (C) 2023 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/>.
|
||||
-->
|
||||
|
||||
<ion-app style="margin-top: env(safe-area-inset-top)">
|
||||
<stapps-navigation></stapps-navigation>
|
||||
</ion-app>
|
||||
109
frontend/app/src/app/app.component.spec.ts
Normal file
109
frontend/app/src/app/app.component.spec.ts
Normal file
@@ -0,0 +1,109 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import {CUSTOM_ELEMENTS_SCHEMA} from '@angular/core';
|
||||
import {TestBed} from '@angular/core/testing';
|
||||
|
||||
import {Platform} from '@ionic/angular';
|
||||
|
||||
import {TranslateService} from '@ngx-translate/core';
|
||||
import {ThingTranslateService} from './translation/thing-translate.service';
|
||||
import {HttpClientTestingModule} from '@angular/common/http/testing';
|
||||
import {AppComponent} from './app.component';
|
||||
import {AuthModule} from './modules/auth/auth.module';
|
||||
import {ConfigProvider} from './modules/config/config.provider';
|
||||
import {SettingsProvider} from './modules/settings/settings.provider';
|
||||
import {NGXLogger} from 'ngx-logger';
|
||||
import {RouterTestingModule} from '@angular/router/testing';
|
||||
import {ScheduleSyncService} from './modules/background/schedule/schedule-sync.service';
|
||||
import {sampleAuthConfiguration} from './_helpers/data/sample-configuration';
|
||||
import {StorageProvider} from './modules/storage/storage.provider';
|
||||
import {SimpleBrowser} from './util/browser.factory';
|
||||
|
||||
describe('AppComponent', () => {
|
||||
let platformReadySpy: any;
|
||||
let platformSpy: jasmine.SpyObj<Platform>;
|
||||
let translateServiceSpy: jasmine.SpyObj<TranslateService>;
|
||||
let thingTranslateServiceSpy: jasmine.SpyObj<ThingTranslateService>;
|
||||
let settingsProvider: jasmine.SpyObj<SettingsProvider>;
|
||||
let configProvider: jasmine.SpyObj<ConfigProvider>;
|
||||
let ngxLogger: jasmine.SpyObj<NGXLogger>;
|
||||
let scheduleSyncServiceSpy: jasmine.SpyObj<ScheduleSyncService>;
|
||||
let platformIsSpy;
|
||||
let storageProvider: jasmine.SpyObj<StorageProvider>;
|
||||
let simpleBrowser: jasmine.SpyObj<SimpleBrowser>;
|
||||
|
||||
beforeEach(() => {
|
||||
platformReadySpy = Promise.resolve();
|
||||
platformIsSpy = Promise.resolve();
|
||||
platformSpy = jasmine.createSpyObj('Platform', {
|
||||
ready: platformReadySpy,
|
||||
is: platformIsSpy,
|
||||
});
|
||||
translateServiceSpy = jasmine.createSpyObj('TranslateService', ['setDefaultLang', 'use']);
|
||||
thingTranslateServiceSpy = jasmine.createSpyObj('ThingTranslateService', ['init']);
|
||||
settingsProvider = jasmine.createSpyObj('SettingsProvider', [
|
||||
'getSettingValue',
|
||||
'provideSetting',
|
||||
'setCategoriesOrder',
|
||||
]);
|
||||
scheduleSyncServiceSpy = jasmine.createSpyObj('ScheduleSyncService', [
|
||||
'getDifferences',
|
||||
'postDifferencesNotification',
|
||||
]);
|
||||
configProvider = jasmine.createSpyObj('ConfigProvider', ['init', 'getAnyValue']);
|
||||
configProvider.getAnyValue = jasmine.createSpy().and.callFake(function () {
|
||||
return sampleAuthConfiguration;
|
||||
});
|
||||
ngxLogger = jasmine.createSpyObj('NGXLogger', ['log', 'error', 'warn']);
|
||||
storageProvider = jasmine.createSpyObj('StorageProvider', ['init', 'get', 'has', 'put']);
|
||||
simpleBrowser = jasmine.createSpyObj('SimpleBrowser', ['open']);
|
||||
|
||||
TestBed.configureTestingModule({
|
||||
imports: [RouterTestingModule.withRoutes([]), HttpClientTestingModule, AuthModule],
|
||||
declarations: [AppComponent],
|
||||
providers: [
|
||||
{provide: Platform, useValue: platformSpy},
|
||||
{provide: TranslateService, useValue: translateServiceSpy},
|
||||
{provide: ThingTranslateService, useValue: thingTranslateServiceSpy},
|
||||
{provide: ScheduleSyncService, useValue: scheduleSyncServiceSpy},
|
||||
{provide: SettingsProvider, useValue: settingsProvider},
|
||||
{provide: ConfigProvider, useValue: configProvider},
|
||||
{provide: NGXLogger, useValue: ngxLogger},
|
||||
{provide: StorageProvider, useValue: storageProvider},
|
||||
{provide: SimpleBrowser, useValue: simpleBrowser},
|
||||
],
|
||||
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
||||
}).compileComponents();
|
||||
});
|
||||
|
||||
it('should create the app', () => {
|
||||
const fixture = TestBed.createComponent(AppComponent);
|
||||
const app = fixture.debugElement.componentInstance;
|
||||
expect(app).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should initialize the app', async () => {
|
||||
TestBed.createComponent(AppComponent);
|
||||
expect(platformSpy.ready).toHaveBeenCalled();
|
||||
// await platformReadySpy;
|
||||
|
||||
// TODO: https://capacitorjs.com/docs/guides/mocking-plugins
|
||||
// expect(splashScreenSpy.hide).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
// TODO: add more tests!
|
||||
});
|
||||
167
frontend/app/src/app/app.component.ts
Normal file
167
frontend/app/src/app/app.component.ts
Normal file
@@ -0,0 +1,167 @@
|
||||
/*
|
||||
* Copyright (C) 2023 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 {AfterContentInit, Component, NgZone} from '@angular/core';
|
||||
import {Router} from '@angular/router';
|
||||
import {App, URLOpenListenerEvent} from '@capacitor/app';
|
||||
import {Platform, ToastController} from '@ionic/angular';
|
||||
import {SettingsProvider} from './modules/settings/settings.provider';
|
||||
import {AuthHelperService} from './modules/auth/auth-helper.service';
|
||||
import {environment} from '../environments/environment';
|
||||
import {StatusBar, Style} from '@capacitor/status-bar';
|
||||
import {Capacitor} from '@capacitor/core';
|
||||
import {ScheduleSyncService} from './modules/background/schedule/schedule-sync.service';
|
||||
import {NavigationBar} from '@hugotomazi/capacitor-navigation-bar';
|
||||
import {Keyboard, KeyboardResize} from '@capacitor/keyboard';
|
||||
|
||||
/**
|
||||
* TODO
|
||||
*/
|
||||
@Component({
|
||||
selector: 'app-root',
|
||||
templateUrl: 'app.component.html',
|
||||
})
|
||||
export class AppComponent implements AfterContentInit {
|
||||
/**
|
||||
* TODO
|
||||
*/
|
||||
pages: Array<{
|
||||
/**
|
||||
* TODO
|
||||
*/
|
||||
component: unknown;
|
||||
/**
|
||||
* TODO
|
||||
*/
|
||||
title: string;
|
||||
}>;
|
||||
|
||||
/**
|
||||
* Angular component selectors that should not infulence keyboard state
|
||||
*/
|
||||
ommitedEventSources = ['ion-input', 'ion-searchbar'];
|
||||
|
||||
/**
|
||||
*
|
||||
* @param platform TODO
|
||||
* @param settingsProvider TODO
|
||||
* @param router The angular router
|
||||
* @param zone The angular zone
|
||||
* @param authHelper Helper service for OAuth providers
|
||||
* @param toastController Toast controller
|
||||
*/
|
||||
constructor(
|
||||
private readonly platform: Platform,
|
||||
private readonly settingsProvider: SettingsProvider,
|
||||
private readonly router: Router,
|
||||
private readonly zone: NgZone,
|
||||
private readonly authHelper: AuthHelperService,
|
||||
private readonly toastController: ToastController,
|
||||
private readonly scheduleSyncService: ScheduleSyncService,
|
||||
) {
|
||||
void this.initializeApp();
|
||||
}
|
||||
|
||||
ngAfterContentInit(): void {
|
||||
this.scheduleSyncService.init();
|
||||
void this.scheduleSyncService.enable();
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO
|
||||
*/
|
||||
async initializeApp() {
|
||||
App.addListener('appUrlOpen', (event: URLOpenListenerEvent) => {
|
||||
this.zone.run(() => {
|
||||
const slug = event.url.split(environment.app_host).pop();
|
||||
if (slug) {
|
||||
this.router.navigateByUrl(slug);
|
||||
}
|
||||
// If no match, do nothing - let regular routing
|
||||
// logic take over
|
||||
});
|
||||
});
|
||||
this.platform.ready().then(async () => {
|
||||
if (Capacitor.isNativePlatform()) {
|
||||
await StatusBar.setStyle({style: Style.Dark});
|
||||
if (Capacitor.getPlatform() === 'android') {
|
||||
await StatusBar.setBackgroundColor({
|
||||
color: getComputedStyle(document.documentElement).getPropertyValue('--ion-color-primary').trim(),
|
||||
});
|
||||
await StatusBar.setOverlaysWebView({overlay: false});
|
||||
await NavigationBar.setColor({
|
||||
color: getComputedStyle(document.documentElement)
|
||||
.getPropertyValue('--ion-background-color')
|
||||
.trim(),
|
||||
darkButtons: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
await this.authNotificationsInit();
|
||||
|
||||
// set order of categories in settings
|
||||
this.settingsProvider.setCategoriesOrder(['profile', 'privacy', 'credentials', 'others']);
|
||||
});
|
||||
|
||||
window.addEventListener('touchmove', this.touchMoveEvent, true);
|
||||
if (Capacitor.getPlatform() === 'ios') {
|
||||
Keyboard.setResizeMode({mode: KeyboardResize.None});
|
||||
}
|
||||
}
|
||||
|
||||
private async authNotificationsInit() {
|
||||
this.authHelper
|
||||
.getProvider('default')
|
||||
.events$.subscribe(action => this.showMessage(this.authHelper.getAuthMessage('default', action)));
|
||||
this.authHelper
|
||||
.getProvider('paia')
|
||||
.events$.subscribe(action => this.showMessage(this.authHelper.getAuthMessage('paia', action)));
|
||||
}
|
||||
|
||||
private async showMessage(message?: string) {
|
||||
if (typeof message === 'undefined') {
|
||||
return;
|
||||
}
|
||||
const toast = await this.toastController.create({
|
||||
message: message,
|
||||
duration: 2000,
|
||||
color: 'success',
|
||||
});
|
||||
await toast.present();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if keyboard should be dissmissed
|
||||
*/
|
||||
touchMoveEvent = (event: Event): void => {
|
||||
if (
|
||||
this.ommitedEventSources.includes(
|
||||
(event?.target as unknown as Record<string, string>)?.['s-hn']?.toLowerCase(),
|
||||
)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
this.unfocusActiveElement();
|
||||
};
|
||||
|
||||
/**
|
||||
* Loses focus on the currently active element (meant for input fields).
|
||||
* Results in virtual keyboard being dissmissed on native and web plattforms.
|
||||
*/
|
||||
unfocusActiveElement() {
|
||||
const activeElement = document.activeElement;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
(activeElement as any)?.blur();
|
||||
}
|
||||
}
|
||||
215
frontend/app/src/app/app.module.ts
Normal file
215
frontend/app/src/app/app.module.ts
Normal file
@@ -0,0 +1,215 @@
|
||||
/*
|
||||
* Copyright (C) 2023 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 {CommonModule, LocationStrategy, PathLocationStrategy, registerLocaleData} from '@angular/common';
|
||||
import {HTTP_INTERCEPTORS, HttpClient, HttpClientModule} from '@angular/common/http';
|
||||
import localeDe from '@angular/common/locales/de';
|
||||
import {APP_INITIALIZER, NgModule} from '@angular/core';
|
||||
import {BrowserModule} from '@angular/platform-browser';
|
||||
import {RouteReuseStrategy} from '@angular/router';
|
||||
import {IonicModule, IonicRouteStrategy, Platform} from '@ionic/angular';
|
||||
import {TranslateLoader, TranslateModule, TranslateService} from '@ngx-translate/core';
|
||||
import {TranslateHttpLoader} from '@ngx-translate/http-loader';
|
||||
import moment from 'moment';
|
||||
import 'moment/min/locales';
|
||||
import {LoggerModule, NGXLogger, NgxLoggerLevel} from 'ngx-logger';
|
||||
import SwiperCore, {FreeMode, Navigation} from 'swiper';
|
||||
|
||||
import {environment} from '../environments/environment';
|
||||
import {AppRoutingModule} from './app-routing.module';
|
||||
import {AppComponent} from './app.component';
|
||||
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 {DataModule} from './modules/data/data.module';
|
||||
import {HebisModule} from './modules/hebis/hebis.module';
|
||||
import {MapModule} from './modules/map/map.module';
|
||||
import {MenuModule} from './modules/menu/menu.module';
|
||||
import {NewsModule} from './modules/news/news.module';
|
||||
import {ScheduleModule} from './modules/schedule/schedule.module';
|
||||
import {SettingsModule} from './modules/settings/settings.module';
|
||||
import {SettingsProvider} from './modules/settings/settings.provider';
|
||||
import {StorageModule} from './modules/storage/storage.module';
|
||||
import {ThingTranslateModule} from './translation/thing-translate.module';
|
||||
import {UtilModule} from './util/util.module';
|
||||
import {initLogger} from './_helpers/ts-logger';
|
||||
import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
|
||||
import {AboutModule} from './modules/about/about.module';
|
||||
import {FavoritesModule} from './modules/favorites/favorites.module';
|
||||
import {ProfilePageModule} from './modules/profile/profile.module';
|
||||
import {FeedbackModule} from './modules/feedback/feedback.module';
|
||||
import {DebugDataCollectorService} from './modules/data/debug-data-collector.service';
|
||||
import {AuthModule} from './modules/auth/auth.module';
|
||||
import {BackgroundModule} from './modules/background/background.module';
|
||||
import {LibraryModule} from './modules/library/library.module';
|
||||
import {StorageProvider} from './modules/storage/storage.provider';
|
||||
import {AssessmentsModule} from './modules/assessments/assessments.module';
|
||||
import {ServiceHandlerInterceptor} from './_helpers/service-handler.interceptor';
|
||||
import {RoutingStackService} from './util/routing-stack.service';
|
||||
import {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 {NavigationModule} from './modules/menu/navigation/navigation.module';
|
||||
import {browserFactory, SimpleBrowser} from './util/browser.factory';
|
||||
|
||||
registerLocaleData(localeDe);
|
||||
|
||||
SwiperCore.use([FreeMode, Navigation]);
|
||||
|
||||
/**
|
||||
* Initializes data needed on startup
|
||||
*
|
||||
* @param storageProvider provider of the saved data (using framework's storage)
|
||||
* @param logger TODO
|
||||
* @param settingsProvider provider of settings (e.g. language that has been set)
|
||||
* @param configProvider TODO
|
||||
* @param translateService TODO
|
||||
* @param _routingStackService Just for init and to track the stack from the get go
|
||||
*/
|
||||
export function initializerFactory(
|
||||
storageProvider: StorageProvider,
|
||||
logger: NGXLogger,
|
||||
settingsProvider: SettingsProvider,
|
||||
configProvider: ConfigProvider,
|
||||
translateService: TranslateService,
|
||||
_routingStackService: RoutingStackService,
|
||||
defaultAuthService: DefaultAuthService,
|
||||
paiaAuthService: PAIAAuthService,
|
||||
) {
|
||||
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);
|
||||
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({
|
||||
bootstrap: [AppComponent],
|
||||
declarations: [AppComponent],
|
||||
imports: [
|
||||
AboutModule,
|
||||
AppRoutingModule,
|
||||
AuthModule,
|
||||
AssessmentsModule,
|
||||
BackgroundModule,
|
||||
BrowserModule,
|
||||
BrowserAnimationsModule,
|
||||
CatalogModule,
|
||||
CommonModule,
|
||||
ConfigModule,
|
||||
DashboardModule,
|
||||
DataModule,
|
||||
HebisModule,
|
||||
IonicModule.forRoot(),
|
||||
IonIconModule,
|
||||
FavoritesModule,
|
||||
LibraryModule,
|
||||
HttpClientModule,
|
||||
ProfilePageModule,
|
||||
FeedbackModule,
|
||||
MapModule,
|
||||
MenuModule,
|
||||
NavigationModule,
|
||||
NewsModule,
|
||||
ScheduleModule,
|
||||
SettingsModule,
|
||||
StorageModule,
|
||||
ThingTranslateModule.forRoot(),
|
||||
TranslateModule.forRoot({
|
||||
defaultLanguage: 'en',
|
||||
loader: {
|
||||
deps: [HttpClient],
|
||||
provide: TranslateLoader,
|
||||
useFactory: createTranslateLoader,
|
||||
},
|
||||
}),
|
||||
UtilModule,
|
||||
// use maximal logging level when not in production, minimal (log only fatal errors) in production
|
||||
LoggerModule.forRoot({
|
||||
level: environment.production ? NgxLoggerLevel.FATAL : NgxLoggerLevel.TRACE,
|
||||
}),
|
||||
],
|
||||
providers: [
|
||||
{
|
||||
provide: RouteReuseStrategy,
|
||||
useClass: IonicRouteStrategy,
|
||||
},
|
||||
{
|
||||
provide: LocationStrategy,
|
||||
useClass: PathLocationStrategy,
|
||||
},
|
||||
{
|
||||
provide: SimpleBrowser,
|
||||
useFactory: browserFactory,
|
||||
deps: [Platform],
|
||||
},
|
||||
{
|
||||
provide: APP_INITIALIZER,
|
||||
multi: true,
|
||||
deps: [
|
||||
StorageProvider,
|
||||
NGXLogger,
|
||||
SettingsProvider,
|
||||
ConfigProvider,
|
||||
TranslateService,
|
||||
RoutingStackService,
|
||||
DefaultAuthService,
|
||||
PAIAAuthService,
|
||||
],
|
||||
useFactory: initializerFactory,
|
||||
},
|
||||
{
|
||||
provide: HTTP_INTERCEPTORS,
|
||||
useClass: ServiceHandlerInterceptor,
|
||||
multi: true,
|
||||
},
|
||||
],
|
||||
})
|
||||
export class AppModule {
|
||||
constructor(public debugDataCollectorService: DebugDataCollectorService) {}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
* Copyright (C) 2023 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';
|
||||
|
||||
@Component({
|
||||
selector: 'about-changelog',
|
||||
templateUrl: 'about-changelog.html',
|
||||
styleUrls: ['about-changelog.scss', './about-page/about-page.scss'],
|
||||
})
|
||||
export class AboutChangelogComponent {}
|
||||
29
frontend/app/src/app/modules/about/about-changelog.html
Normal file
29
frontend/app/src/app/modules/about/about-changelog.html
Normal file
@@ -0,0 +1,29 @@
|
||||
<!--
|
||||
~ Copyright (C) 2023 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/>.
|
||||
-->
|
||||
|
||||
<ion-header>
|
||||
<ion-toolbar color="primary" mode="ios">
|
||||
<ion-buttons slot="start">
|
||||
<ion-back-button></ion-back-button>
|
||||
</ion-buttons>
|
||||
<ion-title>Changelog</ion-title>
|
||||
<!-- TODO: translation -->
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
<ion-content parallax>
|
||||
<div class="about-changelog">
|
||||
<markdown src="assets/about/CHANGELOG.md"></markdown>
|
||||
</div>
|
||||
</ion-content>
|
||||
18
frontend/app/src/app/modules/about/about-changelog.scss
Normal file
18
frontend/app/src/app/modules/about/about-changelog.scss
Normal file
@@ -0,0 +1,18 @@
|
||||
/*!
|
||||
* Copyright (C) 2021 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/>.
|
||||
*/
|
||||
|
||||
ion-content {
|
||||
--padding-start: 16px;
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* Copyright (C) 2021 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, Input} from '@angular/core';
|
||||
import {License} from './about-licenses.component';
|
||||
|
||||
@Component({
|
||||
selector: 'about-license-modal',
|
||||
templateUrl: 'about-license-modal.html',
|
||||
styleUrls: ['about-license-modal.scss'],
|
||||
})
|
||||
export class AboutLicenseModalComponent {
|
||||
@Input() license: License;
|
||||
|
||||
/**
|
||||
* Action when close is pressed
|
||||
*/
|
||||
@Input() dismissAction: () => void;
|
||||
}
|
||||
30
frontend/app/src/app/modules/about/about-license-modal.html
Normal file
30
frontend/app/src/app/modules/about/about-license-modal.html
Normal file
@@ -0,0 +1,30 @@
|
||||
<!--
|
||||
~ Copyright (C) 2021 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/>.
|
||||
-->
|
||||
<ion-card-header>
|
||||
<ion-card-title>
|
||||
<ion-label>{{ license.licenses }}</ion-label>
|
||||
</ion-card-title>
|
||||
<ion-button fill="clear" (click)="dismissAction()">
|
||||
<ion-label>{{ 'modal.DISMISS' | translate }}</ion-label>
|
||||
</ion-button>
|
||||
</ion-card-header>
|
||||
|
||||
<ion-card-content>
|
||||
<ion-content>
|
||||
<ion-list>
|
||||
<pre>{{ license.licenseText }}</pre>
|
||||
</ion-list>
|
||||
</ion-content>
|
||||
</ion-card-content>
|
||||
34
frontend/app/src/app/modules/about/about-license-modal.scss
Normal file
34
frontend/app/src/app/modules/about/about-license-modal.scss
Normal file
@@ -0,0 +1,34 @@
|
||||
/*!
|
||||
* Copyright (C) 2021 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/>.
|
||||
*/
|
||||
|
||||
ion-card-header {
|
||||
ion-button {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
ion-card-content {
|
||||
height: 100%;
|
||||
|
||||
ion-content {
|
||||
ion-list {
|
||||
pre {
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* Copyright (C) 2023 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, OnInit} from '@angular/core';
|
||||
import {ModalController} from '@ionic/angular';
|
||||
import {AboutLicenseModalComponent} from './about-license-modal.component';
|
||||
import licensesFile from 'src/assets/about/licenses.json';
|
||||
|
||||
export interface License {
|
||||
name: string;
|
||||
licenses: string;
|
||||
repository: string;
|
||||
authors?: string;
|
||||
publisher?: string;
|
||||
email?: string;
|
||||
url?: string;
|
||||
licenseText?: string;
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'about-changelog',
|
||||
templateUrl: 'about-licenses.html',
|
||||
styleUrls: ['about-licenses.scss', './about-page/about-page.scss'],
|
||||
})
|
||||
export class AboutLicensesComponent implements OnInit {
|
||||
licenses: License[];
|
||||
|
||||
constructor(private modalController: ModalController) {}
|
||||
|
||||
ngOnInit() {
|
||||
this.licenses = this.loadLicenses();
|
||||
}
|
||||
|
||||
async viewLicense(license: License) {
|
||||
const modal = await this.modalController.create({
|
||||
component: AboutLicenseModalComponent,
|
||||
componentProps: {
|
||||
license: license,
|
||||
dismissAction: () => {
|
||||
modal.dismiss();
|
||||
},
|
||||
},
|
||||
canDismiss: true,
|
||||
});
|
||||
return await modal.present();
|
||||
}
|
||||
|
||||
loadLicenses(): License[] {
|
||||
return licensesFile as License[];
|
||||
}
|
||||
}
|
||||
51
frontend/app/src/app/modules/about/about-licenses.html
Normal file
51
frontend/app/src/app/modules/about/about-licenses.html
Normal file
@@ -0,0 +1,51 @@
|
||||
<!--
|
||||
~ Copyright (C) 2023 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/>.
|
||||
-->
|
||||
|
||||
<ion-header>
|
||||
<ion-toolbar color="primary" mode="ios">
|
||||
<ion-buttons slot="start">
|
||||
<ion-back-button></ion-back-button>
|
||||
</ion-buttons>
|
||||
<ion-title>Licenses</ion-title>
|
||||
<!-- TODO: translation -->
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
<ion-content parallax>
|
||||
<div class="licenses-content">
|
||||
<ion-card
|
||||
*ngFor="let license of licenses"
|
||||
[href]="license.url || license.repository"
|
||||
rel="external"
|
||||
target="_blank"
|
||||
>
|
||||
<ion-card-header>
|
||||
<ion-card-title>
|
||||
{{ license.name }}
|
||||
<ion-icon size="16" weight="300" class="supertext-icon" name="open_in_browser"></ion-icon>
|
||||
</ion-card-title>
|
||||
|
||||
<ion-card-subtitle *ngIf="license.authors || license.publisher">
|
||||
{{ license.authors || license.publisher }}
|
||||
</ion-card-subtitle>
|
||||
</ion-card-header>
|
||||
<ion-card-content>
|
||||
<ion-chip (click)="$event.preventDefault(); viewLicense(license)">
|
||||
<ion-icon name="copyright"></ion-icon>
|
||||
<ion-label>{{ license.licenses }} License</ion-label>
|
||||
</ion-chip>
|
||||
</ion-card-content>
|
||||
</ion-card>
|
||||
</div>
|
||||
</ion-content>
|
||||
38
frontend/app/src/app/modules/about/about-licenses.scss
Normal file
38
frontend/app/src/app/modules/about/about-licenses.scss
Normal file
@@ -0,0 +1,38 @@
|
||||
/*!
|
||||
* Copyright (C) 2023 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/>.
|
||||
*/
|
||||
|
||||
ion-content > div {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
cdk-virtual-scroll-viewport {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
:host ::ng-deep {
|
||||
.cdk-virtual-scroll-content-wrapper {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.virtual-scroll-expander {
|
||||
clear: both;
|
||||
}
|
||||
|
||||
.supertext-icon {
|
||||
vertical-align: text-top;
|
||||
height: 14px;
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright (C) 2021 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, Input} from '@angular/core';
|
||||
import {SCAboutPageContent} from '@openstapps/core';
|
||||
|
||||
@Component({
|
||||
selector: 'about-page-content',
|
||||
templateUrl: 'about-page-content.html',
|
||||
styleUrls: ['about-page-content.scss'],
|
||||
})
|
||||
export class AboutPageContentComponent {
|
||||
@Input() content: SCAboutPageContent;
|
||||
|
||||
isSimpleTextContent(content: unknown | string): content is string {
|
||||
return typeof content === 'string';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
<!--
|
||||
~ Copyright (C) 2023 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/>.
|
||||
-->
|
||||
|
||||
<div [ngSwitch]="content.type">
|
||||
<markdown [data]="'value' | translateSimple: content" *ngSwitchCase="'markdown'"></markdown>
|
||||
<div *ngSwitchCase="'section'">
|
||||
<ion-card *ngIf="content.card; else noCard">
|
||||
<ion-card-header>
|
||||
<ion-card-title>{{ 'title' | translateSimple: content }}</ion-card-title>
|
||||
</ion-card-header>
|
||||
<ion-card-content>
|
||||
<about-page-content [content]="content.content"></about-page-content>
|
||||
</ion-card-content>
|
||||
</ion-card>
|
||||
<ng-template #noCard>
|
||||
<h2>{{ 'title' | translateSimple: content }}</h2>
|
||||
<about-page-content [content]="content.content"></about-page-content>
|
||||
</ng-template>
|
||||
</div>
|
||||
<ion-grid *ngSwitchCase="'table'">
|
||||
<ion-row *ngFor="let row of content.rows">
|
||||
<ion-col *ngFor="let col of row">
|
||||
<about-page-content [content]="col"></about-page-content>
|
||||
</ion-col>
|
||||
</ion-row>
|
||||
</ion-grid>
|
||||
<ion-item *ngSwitchCase="'router link'" [routerLink]="content.link" fill="clear">
|
||||
<ion-icon *ngIf="content.icon" [name]="content.icon" slot="start"></ion-icon>
|
||||
<ion-label>{{ 'title' | translateSimple: content }}</ion-label>
|
||||
</ion-item>
|
||||
</div>
|
||||
@@ -0,0 +1,14 @@
|
||||
/*!
|
||||
* Copyright (C) 2023 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/>.
|
||||
*/
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* 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 {Component, OnInit} from '@angular/core';
|
||||
import {ActivatedRoute} from '@angular/router';
|
||||
import {SCAboutPage, SCAppConfiguration} from '@openstapps/core';
|
||||
import {ConfigProvider} from '../../config/config.provider';
|
||||
import packageJson from '../../../../../package.json';
|
||||
import config from 'capacitor.config';
|
||||
|
||||
@Component({
|
||||
selector: 'about-page',
|
||||
templateUrl: 'about-page.html',
|
||||
styleUrls: ['about-page.scss'],
|
||||
})
|
||||
export class AboutPageComponent implements OnInit {
|
||||
content: SCAboutPage;
|
||||
|
||||
appName = config.appName;
|
||||
|
||||
version = packageJson.version;
|
||||
|
||||
constructor(private readonly route: ActivatedRoute, private readonly configProvider: ConfigProvider) {}
|
||||
|
||||
async ngOnInit() {
|
||||
const route = this.route.snapshot.url.map(it => it.path).join('/');
|
||||
this.content =
|
||||
(this.configProvider.getValue('aboutPages') as SCAppConfiguration['aboutPages'])[route] ?? {};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
<!--
|
||||
~ Copyright (C) 2023 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/>.
|
||||
-->
|
||||
|
||||
<ion-header>
|
||||
<ion-toolbar color="primary" mode="ios">
|
||||
<ion-buttons slot="start">
|
||||
<ion-back-button></ion-back-button>
|
||||
</ion-buttons>
|
||||
<ion-title *ngIf="content; else titleLoading">{{ 'title' | translateSimple: content }}</ion-title>
|
||||
<ng-template #titleLoading>
|
||||
<ion-title><ion-skeleton-text animated="true"></ion-skeleton-text></ion-title>
|
||||
</ng-template>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
<ion-content parallax *ngIf="content">
|
||||
<ion-text>{{ appName }} v{{ version }}</ion-text>
|
||||
<div class="page-content">
|
||||
<about-page-content *ngFor="let element of content.content" [content]="element"></about-page-content>
|
||||
</div>
|
||||
</ion-content>
|
||||
@@ -0,0 +1,91 @@
|
||||
/*!
|
||||
* Copyright (C) 2023 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 'src/theme/util/_mixins.scss';
|
||||
|
||||
ion-text {
|
||||
margin-inline: var(--spacing-md);
|
||||
}
|
||||
|
||||
:host ::ng-deep {
|
||||
ion-card {
|
||||
margin: 0;
|
||||
box-shadow: none;
|
||||
ion-card-content {
|
||||
h1 {
|
||||
margin: 0;
|
||||
}
|
||||
padding-bottom: 8px;
|
||||
}
|
||||
ion-card-header {
|
||||
color: var(--ion-color-dark);
|
||||
padding-top: 8px;
|
||||
padding-bottom: 4px;
|
||||
font-weight: bold;
|
||||
}
|
||||
ion-grid,
|
||||
ion-col {
|
||||
padding-inline-start: 0;
|
||||
padding-top: 0;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
}
|
||||
ion-grid,
|
||||
ion-col {
|
||||
padding-inline-start: 0;
|
||||
padding-top: 0;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
p,
|
||||
h3,
|
||||
h2,
|
||||
h1 {
|
||||
margin-inline: var(--spacing-md);
|
||||
}
|
||||
|
||||
.about-changelog,
|
||||
.licenses-content,
|
||||
.page-content {
|
||||
margin: var(--spacing-md);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--spacing-sm);
|
||||
background: var(--ion-item-background);
|
||||
|
||||
padding-block-end: var(--spacing-md);
|
||||
@include border-radius-in-parallax(var(--border-radius-default));
|
||||
|
||||
& > * {
|
||||
ion-card-subtitle {
|
||||
font-size: var(--font-size-lg);
|
||||
color: var(--ion-color-light-contrast);
|
||||
}
|
||||
|
||||
display: block;
|
||||
@include border-radius-in-parallax(var(--border-radius-default));
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
margin: 0;
|
||||
|
||||
& > ion-thumbnail {
|
||||
background: var(--ion-color-primary);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ion-text {
|
||||
color: var(--ion-color-primary-contrast);
|
||||
}
|
||||
69
frontend/app/src/app/modules/about/about.module.ts
Normal file
69
frontend/app/src/app/modules/about/about.module.ts
Normal file
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* 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 {RouterModule, Routes} from '@angular/router';
|
||||
import {NgModule} from '@angular/core';
|
||||
import {CommonModule} from '@angular/common';
|
||||
import {FormsModule} from '@angular/forms';
|
||||
import {IonicModule} from '@ionic/angular';
|
||||
import {TranslateModule} from '@ngx-translate/core';
|
||||
import {ThingTranslateModule} from '../../translation/thing-translate.module';
|
||||
import {ConfigProvider} from '../config/config.provider';
|
||||
import {AboutPageComponent} from './about-page/about-page.component';
|
||||
import {MarkdownModule} from 'ngx-markdown';
|
||||
import {AboutPageContentComponent} from './about-page/about-page-content.component';
|
||||
import {AboutLicensesComponent} from './about-licenses.component';
|
||||
import {DataModule} from '../data/data.module';
|
||||
import {ScrollingModule} from '@angular/cdk/scrolling';
|
||||
import {AboutLicenseModalComponent} from './about-license-modal.component';
|
||||
import {AboutChangelogComponent} from './about-changelog.component';
|
||||
import {UtilModule} from '../../util/util.module';
|
||||
import {IonIconModule} from '../../util/ion-icon/ion-icon.module';
|
||||
|
||||
const settingsRoutes: Routes = [
|
||||
{path: 'about', component: AboutPageComponent},
|
||||
{path: 'about/changelog', component: AboutChangelogComponent},
|
||||
{path: 'about/imprint', component: AboutPageComponent},
|
||||
{path: 'about/privacy', component: AboutPageComponent},
|
||||
{path: 'about/terms', component: AboutPageComponent},
|
||||
{path: 'about/licenses', component: AboutLicensesComponent},
|
||||
];
|
||||
|
||||
/**
|
||||
* Settings Module
|
||||
*/
|
||||
@NgModule({
|
||||
declarations: [
|
||||
AboutPageComponent,
|
||||
AboutPageContentComponent,
|
||||
AboutLicensesComponent,
|
||||
AboutLicenseModalComponent,
|
||||
AboutChangelogComponent,
|
||||
],
|
||||
imports: [
|
||||
CommonModule,
|
||||
IonIconModule,
|
||||
FormsModule,
|
||||
IonicModule.forRoot(),
|
||||
TranslateModule.forChild(),
|
||||
ThingTranslateModule.forChild(),
|
||||
RouterModule.forChild(settingsRoutes),
|
||||
MarkdownModule,
|
||||
DataModule,
|
||||
ScrollingModule,
|
||||
UtilModule,
|
||||
],
|
||||
providers: [ConfigProvider],
|
||||
})
|
||||
export class AboutModule {}
|
||||
1769
frontend/app/src/app/modules/assessments/assessment-mock-data.json
Normal file
1769
frontend/app/src/app/modules/assessments/assessment-mock-data.json
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
* 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 {NgModule} from '@angular/core';
|
||||
import {AssessmentListItemComponent} from './types/assessment/assessment-list-item.component';
|
||||
import {AssessmentBaseInfoComponent} from './types/assessment/assessment-base-info.component';
|
||||
import {AssessmentDetailComponent} from './types/assessment/assessment-detail.component';
|
||||
import {CommonModule} from '@angular/common';
|
||||
import {FormsModule} from '@angular/forms';
|
||||
import {IonicModule} from '@ionic/angular';
|
||||
import {TranslateModule} from '@ngx-translate/core';
|
||||
import {DataModule} from '../data/data.module';
|
||||
import {ThingTranslateModule} from '../../translation/thing-translate.module';
|
||||
import {CourseOfStudyAssessmentComponent} from './types/course-of-study/course-of-study-assessment.component';
|
||||
import {AssessmentsPageComponent} from './page/assessments-page.component';
|
||||
import {RouterModule} from '@angular/router';
|
||||
import {AuthGuardService} from '../auth/auth-guard.service';
|
||||
import {MomentModule} from 'ngx-moment';
|
||||
import {AssessmentsListItemComponent} from './list/assessments-list-item.component';
|
||||
import {AssessmentsDataListComponent} from './list/assessments-data-list.component';
|
||||
import {AssessmentsDetailComponent} from './detail/assessments-detail.component';
|
||||
import {AssessmentsProvider} from './assessments.provider';
|
||||
import {AssessmentsSimpleDataListComponent} from './list/assessments-simple-data-list.component';
|
||||
import {ProtectedRoutes} from '../auth/protected.routes';
|
||||
import {AssessmentsTreeListComponent} from './list/assessments-tree-list.component';
|
||||
import {IonIconModule} from '../../util/ion-icon/ion-icon.module';
|
||||
import {UtilModule} from '../../util/util.module';
|
||||
|
||||
const routes: ProtectedRoutes = [
|
||||
{
|
||||
path: 'assessments',
|
||||
component: AssessmentsPageComponent,
|
||||
data: {authProvider: 'default'},
|
||||
canActivate: [AuthGuardService],
|
||||
},
|
||||
{
|
||||
path: 'assessments/detail/:uid',
|
||||
component: AssessmentsDetailComponent,
|
||||
data: {authProvider: 'default'},
|
||||
canActivate: [AuthGuardService],
|
||||
},
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
AssessmentListItemComponent,
|
||||
AssessmentBaseInfoComponent,
|
||||
AssessmentDetailComponent,
|
||||
AssessmentsListItemComponent,
|
||||
AssessmentsTreeListComponent,
|
||||
CourseOfStudyAssessmentComponent,
|
||||
AssessmentsPageComponent,
|
||||
AssessmentsDataListComponent,
|
||||
AssessmentsDetailComponent,
|
||||
AssessmentsSimpleDataListComponent,
|
||||
],
|
||||
imports: [
|
||||
CommonModule,
|
||||
FormsModule,
|
||||
IonIconModule,
|
||||
IonicModule,
|
||||
RouterModule.forChild(routes),
|
||||
TranslateModule,
|
||||
DataModule,
|
||||
ThingTranslateModule,
|
||||
MomentModule,
|
||||
UtilModule,
|
||||
],
|
||||
providers: [AssessmentsProvider],
|
||||
exports: [],
|
||||
})
|
||||
export class AssessmentsModule {}
|
||||
115
frontend/app/src/app/modules/assessments/assessments.provider.ts
Normal file
115
frontend/app/src/app/modules/assessments/assessments.provider.ts
Normal file
@@ -0,0 +1,115 @@
|
||||
/*
|
||||
* 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 {ConfigProvider} from '../config/config.provider';
|
||||
import {SCAssessment, SCUuid} from '@openstapps/core';
|
||||
import {DefaultAuthService} from '../auth/default-auth.service';
|
||||
import {HttpClient} from '@angular/common/http';
|
||||
import {uniqBy} from '../../_helpers/collections/uniq';
|
||||
import {keyBy} from '../../_helpers/collections/key-by';
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
export function toAssessmentMap(data: SCAssessment[]): Record<SCUuid, SCAssessment> {
|
||||
return keyBy(
|
||||
uniqBy(
|
||||
[
|
||||
...data,
|
||||
...data.flatMap<SCAssessment>(
|
||||
assessment =>
|
||||
[...(assessment.superAssessments ?? [])]
|
||||
.reverse()
|
||||
.map<SCAssessment>((superAssessment, index, array) => {
|
||||
const superAssessmentCopy = {
|
||||
...superAssessment,
|
||||
} as SCAssessment;
|
||||
superAssessmentCopy.origin = assessment.origin;
|
||||
superAssessmentCopy.superAssessments = array.slice(index + 1).reverse();
|
||||
return superAssessmentCopy;
|
||||
}) ?? [],
|
||||
),
|
||||
] as SCAssessment[],
|
||||
it => it.uid,
|
||||
),
|
||||
it => it.uid,
|
||||
);
|
||||
}
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class AssessmentsProvider {
|
||||
assessmentPath = 'assessments';
|
||||
|
||||
// usually this wouldn't be necessary, but the assessment service
|
||||
// is very aggressive about too many requests being made to the server
|
||||
cache?: Promise<SCAssessment[]>;
|
||||
|
||||
assessments: Promise<Record<SCUuid, SCAssessment>>;
|
||||
|
||||
cacheTimestamp = 0;
|
||||
|
||||
// 15 minutes
|
||||
cacheMaxAge = 15 * 60 * 1000;
|
||||
|
||||
constructor(
|
||||
readonly configProvider: ConfigProvider,
|
||||
readonly defaultAuth: DefaultAuthService,
|
||||
readonly http: HttpClient,
|
||||
) {}
|
||||
|
||||
async getAssessment(uid: SCUuid, accessToken?: string | null, forceFetch = false): Promise<SCAssessment> {
|
||||
await this.getAssessments(accessToken, forceFetch);
|
||||
|
||||
return (await this.assessments)[uid];
|
||||
}
|
||||
|
||||
async getAssessments(accessToken?: string | null, forceFetch = false): Promise<SCAssessment[]> {
|
||||
// again, this is a hack to get around the fact that the assessment service
|
||||
// is very aggressive how many requests you can make, so it can happen
|
||||
// during development that simply by reloading pages over and over again
|
||||
// the assessment service will block you
|
||||
if (accessToken === 'mock' && !this.cache) {
|
||||
this.cacheTimestamp = Date.now();
|
||||
this.cache = import('./assessment-mock-data.json').then(it => it.data as SCAssessment[]);
|
||||
this.assessments = this.cache.then(toAssessmentMap);
|
||||
}
|
||||
|
||||
if (this.cache && !forceFetch && Date.now() - this.cacheTimestamp < this.cacheMaxAge) {
|
||||
return this.cache;
|
||||
}
|
||||
|
||||
const url = this.configProvider.config.app.features.extern?.hisometry.url;
|
||||
if (!url) throw new Error('Config lacks url for hisometry');
|
||||
|
||||
this.cache = this.http
|
||||
.get<{data: SCAssessment[]}>(`${url}/${this.assessmentPath}`, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${accessToken ?? (await this.defaultAuth.getValidToken()).accessToken}`,
|
||||
},
|
||||
})
|
||||
.toPromise()
|
||||
.then(it => {
|
||||
this.cacheTimestamp = Date.now();
|
||||
|
||||
return it?.data ?? [];
|
||||
});
|
||||
this.assessments = this.cache.then(toAssessmentMap);
|
||||
|
||||
return this.cache;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
* 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 {Component, Input, OnDestroy, OnInit, ViewChild} from '@angular/core';
|
||||
import {ActivatedRoute} from '@angular/router';
|
||||
import {AssessmentsProvider} from '../assessments.provider';
|
||||
import {DataDetailComponent, ExternalDataLoadEvent} from '../../data/detail/data-detail.component';
|
||||
import {NavController, ViewWillEnter} from '@ionic/angular';
|
||||
import {Subscription} from 'rxjs';
|
||||
import {DataRoutingService} from '../../data/data-routing.service';
|
||||
import {SCAssessment} from '@openstapps/core';
|
||||
|
||||
@Component({
|
||||
selector: 'assessments-detail',
|
||||
templateUrl: 'assessments-detail.html',
|
||||
styleUrls: ['assessments-detail.scss'],
|
||||
})
|
||||
export class AssessmentsDetailComponent implements ViewWillEnter, OnInit, OnDestroy {
|
||||
constructor(
|
||||
readonly route: ActivatedRoute,
|
||||
readonly assessmentsProvider: AssessmentsProvider,
|
||||
readonly dataRoutingService: DataRoutingService,
|
||||
readonly navController: NavController,
|
||||
readonly activatedRoute: ActivatedRoute,
|
||||
) {}
|
||||
|
||||
subscriptions: Subscription[] = [];
|
||||
|
||||
@Input() dataPathAutoRouting = true;
|
||||
|
||||
@ViewChild(DataDetailComponent)
|
||||
detailComponent: DataDetailComponent;
|
||||
|
||||
item: SCAssessment;
|
||||
|
||||
ngOnInit() {
|
||||
if (!this.dataPathAutoRouting) return;
|
||||
this.subscriptions.push(
|
||||
this.dataRoutingService.pathSelectListener().subscribe(item => {
|
||||
void this.navController.navigateBack(['assessments', 'detail', item.uid], {
|
||||
queryParams: {
|
||||
token: this.activatedRoute.snapshot.queryParamMap.get('token'),
|
||||
},
|
||||
});
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
for (const sub of this.subscriptions) sub.unsubscribe();
|
||||
}
|
||||
|
||||
getItem(event: ExternalDataLoadEvent) {
|
||||
this.assessmentsProvider
|
||||
.getAssessment(event.uid, this.route.snapshot.queryParamMap.get('token'), event.forceReload)
|
||||
.then(assessment => {
|
||||
this.item = assessment;
|
||||
event.resolve(this.item);
|
||||
});
|
||||
}
|
||||
|
||||
async ionViewWillEnter() {
|
||||
await this.detailComponent.ionViewWillEnter();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
<!--
|
||||
~ 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/>.
|
||||
-->
|
||||
<ion-header>
|
||||
<ion-toolbar color="primary" mode="ios">
|
||||
<ion-buttons slot="start">
|
||||
<ion-back-button></ion-back-button>
|
||||
</ion-buttons>
|
||||
<ion-title>{{ 'data.detail.TITLE' | translate }}</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
<stapps-data-detail
|
||||
[autoRouteDataPath]="false"
|
||||
[externalData]="true"
|
||||
(loadItem)="getItem($event)"
|
||||
[defaultHeader]="false"
|
||||
>
|
||||
<ng-template let-item>
|
||||
<assessment-detail [item]="item"></assessment-detail>
|
||||
</ng-template>
|
||||
</stapps-data-detail>
|
||||
@@ -0,0 +1,18 @@
|
||||
/*!
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
stapps-data-detail {
|
||||
height: 100%;
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* 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 {Component, EventEmitter, Input, Output} from '@angular/core';
|
||||
import {SCThings} from '@openstapps/core';
|
||||
import {Observable} from 'rxjs';
|
||||
|
||||
@Component({
|
||||
selector: 'assessments-data-list',
|
||||
templateUrl: './assessments-data-list.html',
|
||||
styleUrls: ['./assessments-data-list.scss'],
|
||||
})
|
||||
export class AssessmentsDataListComponent {
|
||||
/**
|
||||
* All SCThings to display
|
||||
*/
|
||||
@Input() items?: SCThings[];
|
||||
|
||||
/**
|
||||
* Output binding to trigger pagination fetch
|
||||
*/
|
||||
// eslint-disable-next-line @angular-eslint/no-output-rename
|
||||
@Output('loadmore') loadMore = new EventEmitter<void>();
|
||||
|
||||
/**
|
||||
* Emits when scroll view should reset to top
|
||||
*/
|
||||
@Input() resetToTop?: Observable<void>;
|
||||
|
||||
/**
|
||||
* Indicates whether the list is to display SCThings of a single type
|
||||
*/
|
||||
@Input() singleType = false;
|
||||
|
||||
/**
|
||||
* Signalizes that the data is being loaded
|
||||
*/
|
||||
@Input() loading = true;
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
<!--
|
||||
~ 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/>.
|
||||
-->
|
||||
|
||||
<stapps-data-list
|
||||
[items]="items"
|
||||
[loading]="loading"
|
||||
[singleType]="singleType"
|
||||
[resetToTop]="resetToTop"
|
||||
(loadmore)="loadMore.emit($event)"
|
||||
>
|
||||
<ng-template let-item>
|
||||
<assessments-list-item [item]="item" [hideThumbnail]="singleType"></assessments-list-item>
|
||||
</ng-template>
|
||||
<ng-container header>
|
||||
<ng-content select="[header]"></ng-content>
|
||||
</ng-container>
|
||||
</stapps-data-list>
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* 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 {Component, Input} from '@angular/core';
|
||||
import {SCThings} from '@openstapps/core';
|
||||
|
||||
@Component({
|
||||
selector: 'assessments-list-item',
|
||||
templateUrl: 'assessments-list-item.html',
|
||||
styleUrls: ['assessments-list-item.scss'],
|
||||
})
|
||||
export class AssessmentsListItemComponent {
|
||||
/**
|
||||
* Whether the list item should show a thumbnail
|
||||
*/
|
||||
@Input() hideThumbnail = false;
|
||||
|
||||
/**
|
||||
* An item to show
|
||||
*/
|
||||
@Input() item: SCThings;
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
<!--
|
||||
~ 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/>.
|
||||
-->
|
||||
|
||||
<stapps-data-list-item
|
||||
[item]="item"
|
||||
[hideThumbnail]="hideThumbnail"
|
||||
[lines]="'none'"
|
||||
[favoriteButton]="false"
|
||||
>
|
||||
<ng-template let-data>
|
||||
<stapps-assessment-list-item [item]="data"></stapps-assessment-list-item>
|
||||
</ng-template>
|
||||
</stapps-data-list-item>
|
||||
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
* 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 {Component, Input, OnDestroy, OnInit} from '@angular/core';
|
||||
import {SCThings} from '@openstapps/core';
|
||||
import {Subscription} from 'rxjs';
|
||||
import {DataRoutingService} from '../../data/data-routing.service';
|
||||
import {ActivatedRoute, Router} from '@angular/router';
|
||||
|
||||
@Component({
|
||||
selector: 'assessments-simple-data-list',
|
||||
templateUrl: 'assessments-simple-data-list.html',
|
||||
styleUrls: ['assessments-simple-data-list.scss'],
|
||||
})
|
||||
export class AssessmentsSimpleDataListComponent implements OnInit, OnDestroy {
|
||||
/**
|
||||
* All SCThings to display
|
||||
*/
|
||||
_items?: Promise<SCThings[] | undefined>;
|
||||
|
||||
/**
|
||||
* Indicates whether or not the list is to display SCThings of a single type
|
||||
*/
|
||||
@Input() singleType = false;
|
||||
|
||||
/**
|
||||
* List header
|
||||
*/
|
||||
@Input() listHeader?: string;
|
||||
|
||||
@Input() set items(items: SCThings[] | undefined) {
|
||||
this._items = new Promise(resolve => resolve(items));
|
||||
}
|
||||
|
||||
subscriptions: Subscription[] = [];
|
||||
|
||||
constructor(
|
||||
readonly dataRoutingService: DataRoutingService,
|
||||
readonly router: Router,
|
||||
readonly activatedRoute: ActivatedRoute,
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
this.subscriptions.push(
|
||||
this.dataRoutingService.itemSelectListener().subscribe(thing => {
|
||||
void this.router.navigate(['assessments', 'detail', thing.uid], {
|
||||
queryParams: {
|
||||
token: this.activatedRoute.snapshot.queryParamMap.get('token'),
|
||||
},
|
||||
});
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
for (const subscription of this.subscriptions) subscription.unsubscribe();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
<!--
|
||||
~ 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/>.
|
||||
-->
|
||||
|
||||
<stapps-simple-data-list
|
||||
[singleType]="singleType"
|
||||
[items]="_items"
|
||||
[listHeader]="listHeader"
|
||||
[autoRouting]="false"
|
||||
>
|
||||
<ng-template let-item>
|
||||
<assessments-list-item [item]="item" [hideThumbnail]="singleType"></assessments-list-item>
|
||||
</ng-template>
|
||||
</stapps-simple-data-list>
|
||||
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* 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 {Component, Input} from '@angular/core';
|
||||
import {SCThings} from '@openstapps/core';
|
||||
|
||||
@Component({
|
||||
selector: 'assessments-tree-list',
|
||||
templateUrl: 'assessments-tree-list.html',
|
||||
styleUrls: ['assessments-tree-list.scss'],
|
||||
})
|
||||
export class AssessmentsTreeListComponent {
|
||||
@Input() items?: Promise<SCThings[] | undefined>;
|
||||
|
||||
@Input() singleType = false;
|
||||
|
||||
@Input() groupingKey: string;
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
<!--
|
||||
~ 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/>.
|
||||
-->
|
||||
|
||||
<tree-list [items]="items" [singleType]="singleType" [groupingKey]="groupingKey">
|
||||
<ng-template let-item>
|
||||
<assessments-list-item [item]="item" [hideThumbnail]="singleType"></assessments-list-item>
|
||||
</ng-template>
|
||||
</tree-list>
|
||||
@@ -0,0 +1,14 @@
|
||||
/*!
|
||||
* 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/>.
|
||||
*/
|
||||
@@ -0,0 +1,110 @@
|
||||
/*
|
||||
* 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 {AfterViewInit, Component, OnDestroy, OnInit, ViewChild} from '@angular/core';
|
||||
import {AssessmentsProvider} from '../assessments.provider';
|
||||
import {SCAssessment, SCCourseOfStudy} from '@openstapps/core';
|
||||
import {ActivatedRoute, Router} from '@angular/router';
|
||||
import {Subscription} from 'rxjs';
|
||||
import {NGXLogger} from 'ngx-logger';
|
||||
import {materialSharedAxisX} from '../../../animation/material-motion';
|
||||
import {SharedAxisChoreographer} from '../../../animation/animation-choreographer';
|
||||
import {DataProvider, DataScope} from '../../data/data.provider';
|
||||
import {DataRoutingService} from '../../data/data-routing.service';
|
||||
import {groupBy} from '../../../_helpers/collections/group-by';
|
||||
import {mapValues} from '../../../_helpers/collections/map-values';
|
||||
|
||||
@Component({
|
||||
selector: 'app-assessments-page',
|
||||
templateUrl: 'assessments-page.html',
|
||||
styleUrls: ['assessments-page.scss'],
|
||||
animations: [materialSharedAxisX],
|
||||
})
|
||||
export class AssessmentsPageComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||
assessments: Promise<
|
||||
Record<
|
||||
string,
|
||||
{
|
||||
assessments: SCAssessment[];
|
||||
courseOfStudy: Promise<SCCourseOfStudy | undefined>;
|
||||
}
|
||||
>
|
||||
>;
|
||||
|
||||
assessmentKeys: string[] = [];
|
||||
|
||||
routingSubscription: Subscription;
|
||||
|
||||
@ViewChild('segment') segmentView!: HTMLIonSegmentElement;
|
||||
|
||||
sharedAxisChoreographer: SharedAxisChoreographer<string> = new SharedAxisChoreographer<string>('', []);
|
||||
|
||||
constructor(
|
||||
readonly logger: NGXLogger,
|
||||
readonly assessmentsProvider: AssessmentsProvider,
|
||||
readonly dataProvider: DataProvider,
|
||||
readonly activatedRoute: ActivatedRoute,
|
||||
readonly dataRoutingService: DataRoutingService,
|
||||
readonly router: Router,
|
||||
) {}
|
||||
|
||||
ngAfterViewInit() {
|
||||
this.segmentView.value = this.sharedAxisChoreographer.currentValue;
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.routingSubscription.unsubscribe();
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.routingSubscription = this.dataRoutingService.itemSelectListener().subscribe(thing => {
|
||||
void this.router.navigate(['assessments', 'detail', thing.uid], {
|
||||
queryParams: {
|
||||
token: this.activatedRoute.snapshot.queryParamMap.get('token'),
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
this.activatedRoute.queryParams.subscribe(parameters => {
|
||||
try {
|
||||
this.assessments = this.assessmentsProvider
|
||||
.getAssessments(parameters.token)
|
||||
.then(assessments => groupBy(assessments, it => it.courseOfStudy?.uid ?? 'unknown'))
|
||||
.then(it => {
|
||||
this.assessmentKeys = Object.keys(it);
|
||||
this.sharedAxisChoreographer = new SharedAxisChoreographer(
|
||||
this.assessmentKeys[0],
|
||||
this.assessmentKeys,
|
||||
);
|
||||
if (this.segmentView) {
|
||||
this.segmentView.value = this.sharedAxisChoreographer.currentValue;
|
||||
}
|
||||
return it;
|
||||
})
|
||||
.then(groups =>
|
||||
mapValues(groups, (group, uid) => ({
|
||||
assessments: group,
|
||||
courseOfStudy: this.dataProvider
|
||||
.get(uid, DataScope.Remote)
|
||||
.catch(() => group[0].courseOfStudy) as Promise<SCCourseOfStudy>,
|
||||
})),
|
||||
);
|
||||
} catch (error) {
|
||||
this.logger.error(error);
|
||||
this.assessments = Promise.resolve({});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
<!--
|
||||
~ Copyright (C) 2023 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/>.
|
||||
-->
|
||||
|
||||
<ion-header>
|
||||
<ion-toolbar color="primary" mode="ios">
|
||||
<ion-buttons slot="start">
|
||||
<ion-back-button></ion-back-button>
|
||||
</ion-buttons>
|
||||
<ion-title>{{ 'assessments.TITLE' | translate }}</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
|
||||
<ion-content>
|
||||
<ion-segment
|
||||
#segment
|
||||
[scrollable]="true"
|
||||
mode="md"
|
||||
(ionChange)="sharedAxisChoreographer.changeViewForState(segment.value)"
|
||||
value=""
|
||||
>
|
||||
<ion-segment-button *ngFor="let key of assessmentKeys" [value]="key">
|
||||
<div *ngIf="assessments | async as assessments">
|
||||
<ion-label
|
||||
class="ion-text-wrap"
|
||||
*ngIf="assessments[key].courseOfStudy | async as course; else defaultLabel"
|
||||
>
|
||||
{{ 'name' | thingTranslate: course }} ({{ 'academicDegree' | thingTranslate: course }})
|
||||
</ion-label>
|
||||
</div>
|
||||
<ng-template #defaultLabel>
|
||||
<ion-label>{{ key }}</ion-label>
|
||||
</ng-template>
|
||||
</ion-segment-button>
|
||||
</ion-segment>
|
||||
<div
|
||||
[ngSwitch]="sharedAxisChoreographer.currentValue"
|
||||
[@materialSharedAxisX]="sharedAxisChoreographer.animationState"
|
||||
(@materialSharedAxisX.done)="sharedAxisChoreographer.animationDone()"
|
||||
*ngIf="assessments | async as items"
|
||||
class="content"
|
||||
>
|
||||
<course-of-study-assessment
|
||||
[assessments]="items[sharedAxisChoreographer.currentValue].assessments"
|
||||
[courseOfStudy]="items[sharedAxisChoreographer.currentValue].courseOfStudy | async"
|
||||
></course-of-study-assessment>
|
||||
</div>
|
||||
</ion-content>
|
||||
@@ -0,0 +1,20 @@
|
||||
/*!
|
||||
* Copyright (C) 2023 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/>.
|
||||
*/
|
||||
|
||||
.content {
|
||||
height: 100%;
|
||||
padding-inline: 8px;
|
||||
--ion-item-background: var(--ion-background-color);
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* 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 {Component, Input} from '@angular/core';
|
||||
import {SCAssessment} from '@openstapps/core';
|
||||
|
||||
@Component({
|
||||
selector: 'assessment-base-info',
|
||||
templateUrl: 'assessment-base-info.html',
|
||||
styleUrls: ['assessment-base-info.scss'],
|
||||
})
|
||||
export class AssessmentBaseInfoComponent {
|
||||
_item: SCAssessment;
|
||||
|
||||
passed = false;
|
||||
|
||||
@Input() set item(item: SCAssessment) {
|
||||
this._item = item;
|
||||
this.passed = !/^(5[,.]0)|FX?$/i.test(item.grade);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
<!--
|
||||
~ 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/>.
|
||||
-->
|
||||
|
||||
<ion-label [color]="passed ? undefined : 'danger'"
|
||||
>{{
|
||||
(_item.grade | isNumeric)
|
||||
? (_item.grade | numberLocalized: 'minimumFractionDigits:1,maximumFractionDigits:1')
|
||||
: ''
|
||||
}}
|
||||
{{ 'status' | thingTranslate: _item | titlecase }},
|
||||
{{ 'attempt' | propertyNameTranslate: _item }}
|
||||
{{ _item.attempt }}
|
||||
</ion-label>
|
||||
<ion-note>
|
||||
{{ _item.ects }}
|
||||
{{ 'ects' | propertyNameTranslate: _item }}</ion-note
|
||||
>
|
||||
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* 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 {Component, Input} from '@angular/core';
|
||||
import {SCAssessment} from '@openstapps/core';
|
||||
|
||||
@Component({
|
||||
selector: 'assessment-detail',
|
||||
templateUrl: 'assessment-detail.html',
|
||||
styleUrls: ['assessment-detail.scss'],
|
||||
})
|
||||
export class AssessmentDetailComponent {
|
||||
@Input() item: SCAssessment;
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
<!--
|
||||
~ 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/>.
|
||||
-->
|
||||
|
||||
<ion-card>
|
||||
<ion-card-content>
|
||||
<ion-note *ngIf="item.courseOfStudy as courseOfStudy">
|
||||
{{ $any('courseOfStudy' | propertyNameTranslate: item) | titlecase }}:
|
||||
{{ 'name' | thingTranslate: $any(courseOfStudy) }}
|
||||
({{ 'academicDegree' | thingTranslate: $any(courseOfStudy) }})
|
||||
</ion-note>
|
||||
</ion-card-content>
|
||||
</ion-card>
|
||||
<ion-list class="container">
|
||||
<ion-item lines="none">
|
||||
<assessment-base-info [item]="item"></assessment-base-info>
|
||||
</ion-item>
|
||||
</ion-list>
|
||||
@@ -0,0 +1,4 @@
|
||||
stapps-data-list {
|
||||
height: 100px;
|
||||
width: 100%;
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* 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 {Component, Input} from '@angular/core';
|
||||
import {SCAssessment} from '@openstapps/core';
|
||||
|
||||
@Component({
|
||||
selector: 'stapps-assessment-list-item',
|
||||
templateUrl: './assessment-list-item.html',
|
||||
styleUrls: ['./assessment-list-item.scss'],
|
||||
})
|
||||
export class AssessmentListItemComponent {
|
||||
@Input() item: SCAssessment;
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
<!--
|
||||
~ 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/>.
|
||||
-->
|
||||
|
||||
<div class="container">
|
||||
<h2 class="name">
|
||||
{{ 'name' | thingTranslate: item }}
|
||||
{{ item.date ? (item.date | amDateFormat) : '' }}
|
||||
</h2>
|
||||
<assessment-base-info [item]="item"></assessment-base-info>
|
||||
</div>
|
||||
@@ -0,0 +1,40 @@
|
||||
/*!
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
.column {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.item {
|
||||
height: 72px;
|
||||
}
|
||||
|
||||
.tree-indicator {
|
||||
width: 16px;
|
||||
margin-right: 4px;
|
||||
padding-left: 1px;
|
||||
}
|
||||
|
||||
.super-assessments-list {
|
||||
// prevent the list from hijacking hover overlays
|
||||
z-index: -1;
|
||||
|
||||
:last-child {
|
||||
.tree-indicator-after {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* 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 {Component, Input} from '@angular/core';
|
||||
import {SCAssessment, SCCourseOfStudyWithoutReferences} from '@openstapps/core';
|
||||
|
||||
@Component({
|
||||
selector: 'course-of-study-assessment',
|
||||
templateUrl: 'course-of-study-assessment.html',
|
||||
styleUrls: ['course-of-study-assessment.scss'],
|
||||
})
|
||||
export class CourseOfStudyAssessmentComponent {
|
||||
@Input() courseOfStudy: SCCourseOfStudyWithoutReferences | null;
|
||||
|
||||
_assessments: Promise<SCAssessment[]>;
|
||||
|
||||
@Input() set assessments(value: SCAssessment[]) {
|
||||
this._assessments = Promise.resolve(value);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
<!--
|
||||
~ 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/>.
|
||||
-->
|
||||
|
||||
<section>
|
||||
<h3>
|
||||
{{ 'assessments.courseOfStudyAssessments.ASSESSMENTS' | translate }}
|
||||
</h3>
|
||||
|
||||
<assessments-tree-list [items]="_assessments" [singleType]="true" [groupingKey]="'superAssessments'">
|
||||
</assessments-tree-list>
|
||||
</section>
|
||||
@@ -0,0 +1,14 @@
|
||||
/*!
|
||||
* 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/>.
|
||||
*/
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user