mirror of
https://gitlab.com/openstapps/openstapps.git
synced 2026-03-14 10:42:40 +00:00
feat: timetable module - schedule and calendar
This commit is contained in:
@@ -3,6 +3,7 @@
|
||||
*ngIf="applicable['locate']()"
|
||||
[item]="item"
|
||||
></stapps-locate-action-chip>
|
||||
<!-- Add Event Chip needs to load data and should be the last -->
|
||||
<stapps-add-event-action-chip
|
||||
*ngIf="applicable['event']()"
|
||||
[item]="item"
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
div {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
width: fit-content;
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
/*
|
||||
* Copyright (C) 2021 StApps
|
||||
* This program is free software: you can redistribute it and/or modify it
|
||||
@@ -12,10 +13,29 @@
|
||||
* 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 {ChangeDetectorRef, Component, Input, OnInit} from '@angular/core';
|
||||
import {SCDateSeries} from '@openstapps/core';
|
||||
import {every, groupBy, some, sortBy, values} from 'lodash-es';
|
||||
import {
|
||||
ChangeDetectorRef,
|
||||
Component,
|
||||
Input,
|
||||
OnDestroy,
|
||||
OnInit,
|
||||
} from '@angular/core';
|
||||
import {PopoverController} from '@ionic/angular';
|
||||
import {SCDateSeries, SCUuid} from '@openstapps/core';
|
||||
import {
|
||||
difference,
|
||||
every,
|
||||
flatMap,
|
||||
groupBy,
|
||||
mapValues,
|
||||
some,
|
||||
sortBy,
|
||||
union,
|
||||
values,
|
||||
} from 'lodash-es';
|
||||
import {capitalize, last} from 'lodash-es';
|
||||
import {Subscription} from 'rxjs';
|
||||
import {ScheduleProvider} from '../../schedule/schedule.provider';
|
||||
|
||||
enum Selection {
|
||||
ON = 2,
|
||||
@@ -28,7 +48,6 @@ enum Selection {
|
||||
*
|
||||
* The generic is to preserve type safety of how deep the tree goes.
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
class TreeNode<T extends TreeNode<any> | SelectionValue> {
|
||||
/**
|
||||
* Value of this node
|
||||
@@ -54,19 +73,16 @@ class TreeNode<T extends TreeNode<any> | SelectionValue> {
|
||||
* Accumulate values of children to set current value
|
||||
*/
|
||||
private accumulateApplyValues() {
|
||||
const selections: number[] = this.children.map(
|
||||
it =>
|
||||
/* eslint-disable unicorn/no-nested-ternary */
|
||||
it instanceof TreeNode
|
||||
? it.checked
|
||||
? Selection.ON
|
||||
: it.indeterminate
|
||||
? Selection.PARTIAL
|
||||
: Selection.OFF
|
||||
: (it as SelectionValue).selected
|
||||
const selections: number[] = this.children.map(it =>
|
||||
it instanceof TreeNode
|
||||
? it.checked
|
||||
? Selection.ON
|
||||
: Selection.OFF,
|
||||
/* eslint-enable unicorn/no-nested-ternary */
|
||||
: it.indeterminate
|
||||
? Selection.PARTIAL
|
||||
: Selection.OFF
|
||||
: (it as SelectionValue).selected
|
||||
? Selection.ON
|
||||
: Selection.OFF,
|
||||
);
|
||||
|
||||
this.checked = every(selections, it => it === Selection.ON);
|
||||
@@ -83,7 +99,7 @@ class TreeNode<T extends TreeNode<any> | SelectionValue> {
|
||||
if (child instanceof TreeNode) {
|
||||
child.checked = this.checked;
|
||||
child.indeterminate = false;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
// tslint:disable-next-line:no-any
|
||||
(child as TreeNode<any>).applyValueDownwards();
|
||||
} else {
|
||||
(child as SelectionValue).selected = this.checked;
|
||||
@@ -149,7 +165,7 @@ interface SelectionValue {
|
||||
templateUrl: 'add-event-popover.html',
|
||||
styleUrls: ['add-event-popover.scss'],
|
||||
})
|
||||
export class AddEventPopoverComponent implements OnInit {
|
||||
export class AddEventPopoverComponent implements OnInit, OnDestroy {
|
||||
/**
|
||||
* Lodash alias
|
||||
*/
|
||||
@@ -170,23 +186,70 @@ export class AddEventPopoverComponent implements OnInit {
|
||||
*/
|
||||
selection: TreeNode<TreeNode<SelectionValue>>;
|
||||
|
||||
constructor(readonly ref: ChangeDetectorRef) {}
|
||||
/**
|
||||
* Uuids
|
||||
*/
|
||||
uuids: SCUuid[];
|
||||
|
||||
/**
|
||||
* Uuid Subscription
|
||||
*/
|
||||
uuidSubscription: Subscription;
|
||||
|
||||
constructor(
|
||||
readonly ref: ChangeDetectorRef,
|
||||
readonly scheduleProvider: ScheduleProvider,
|
||||
readonly popoverController: PopoverController,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Destroy
|
||||
*/
|
||||
ngOnDestroy() {
|
||||
this.uuidSubscription.unsubscribe();
|
||||
}
|
||||
|
||||
/**
|
||||
* Init
|
||||
*/
|
||||
ngOnInit() {
|
||||
this.selection = new TreeNode(
|
||||
values(
|
||||
groupBy(
|
||||
sortBy(
|
||||
this.items.map(item => ({selected: false, item: item})),
|
||||
it => it.item.frequency,
|
||||
),
|
||||
it => it.item.frequency,
|
||||
),
|
||||
).map(item => new TreeNode(item, this.ref)),
|
||||
this.ref,
|
||||
this.uuidSubscription = this.scheduleProvider.uuids$.subscribe(
|
||||
async result => {
|
||||
this.uuids = result;
|
||||
|
||||
this.selection = new TreeNode(
|
||||
values(
|
||||
groupBy(
|
||||
sortBy(
|
||||
this.items.map(item => ({
|
||||
selected: this.uuids.includes(item.uid),
|
||||
item: item,
|
||||
})),
|
||||
it => it.item.frequency,
|
||||
),
|
||||
it => it.item.frequency,
|
||||
),
|
||||
).map(item => new TreeNode(item, this.ref)),
|
||||
this.ref,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* On selection change
|
||||
*/
|
||||
async onCommit(save: boolean) {
|
||||
if (save) {
|
||||
const {false: unselected, true: selected} = mapValues(
|
||||
groupBy(flatMap(this.selection.children, 'children'), 'selected'),
|
||||
value => value.map(it => it.item.uid),
|
||||
);
|
||||
this.scheduleProvider.uuids$.next(
|
||||
union(difference(this.uuids, unselected), selected),
|
||||
);
|
||||
}
|
||||
|
||||
await this.popoverController.dismiss();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,4 +45,12 @@
|
||||
</ion-item>
|
||||
</ion-item-group>
|
||||
</ion-item-group>
|
||||
<div class="action-buttons">
|
||||
<ion-button (click)="onCommit(false)" fill="clear">{{
|
||||
'abort' | translate
|
||||
}}</ion-button>
|
||||
<ion-button (click)="onCommit(true)" fill="clear">{{
|
||||
'ok' | translate
|
||||
}}</ion-button>
|
||||
</div>
|
||||
</ion-card-content>
|
||||
|
||||
@@ -5,3 +5,7 @@
|
||||
ion-card-content {
|
||||
width: fit-content;
|
||||
}
|
||||
|
||||
.action-buttons {
|
||||
float: right;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* eslint-disable class-methods-use-this */
|
||||
/* tslint:disable:prefer-function-over-method */
|
||||
/*
|
||||
* Copyright (C) 2021 StApps
|
||||
* This program is free software: you can redistribute it and/or modify it
|
||||
@@ -13,11 +13,18 @@
|
||||
* 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, OnInit} from '@angular/core';
|
||||
import {Component, Input, OnDestroy, OnInit} from '@angular/core';
|
||||
import {PopoverController} from '@ionic/angular';
|
||||
import {SCDateSeries, SCThing, SCThingType} from '@openstapps/core';
|
||||
import {DataProvider} from '../../data.provider';
|
||||
import {SCDateSeries, SCThing, SCThingType, SCUuid} from '@openstapps/core';
|
||||
import {difference, map} from 'lodash-es';
|
||||
import {Subscription} from 'rxjs';
|
||||
import {ScheduleProvider} from '../../../schedule/schedule.provider';
|
||||
import {AddEventPopoverComponent} from '../add-event-popover.component';
|
||||
import {CoordinatedSearchProvider} from '../../coordinated-search.provider';
|
||||
import {
|
||||
chipSkeletonTransition,
|
||||
chipTransition,
|
||||
} from '../../../../animation/skeleton-transitions/chip-loading-transition';
|
||||
|
||||
enum AddEventStates {
|
||||
ADDED_ALL,
|
||||
@@ -33,8 +40,9 @@ enum AddEventStates {
|
||||
selector: 'stapps-add-event-action-chip',
|
||||
templateUrl: 'add-event-action-chip.html',
|
||||
styleUrls: ['add-event-action-chip.scss'],
|
||||
animations: [chipSkeletonTransition, chipTransition],
|
||||
})
|
||||
export class AddEventActionChipComponent implements OnInit {
|
||||
export class AddEventActionChipComponent implements OnInit, OnDestroy {
|
||||
/**
|
||||
* Associated date series
|
||||
*/
|
||||
@@ -91,9 +99,20 @@ export class AddEventActionChipComponent implements OnInit {
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* UUIDs
|
||||
*/
|
||||
uuids: SCUuid[];
|
||||
|
||||
/**
|
||||
* UUID Subscription
|
||||
*/
|
||||
uuidSubscription: Subscription;
|
||||
|
||||
constructor(
|
||||
readonly popoverController: PopoverController,
|
||||
readonly dataProvider: DataProvider,
|
||||
readonly dataProvider: CoordinatedSearchProvider,
|
||||
readonly scheduleProvider: ScheduleProvider,
|
||||
) {}
|
||||
|
||||
/**
|
||||
@@ -107,6 +126,13 @@ export class AddEventActionChipComponent implements OnInit {
|
||||
this.disabled = disabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO
|
||||
*/
|
||||
ngOnDestroy() {
|
||||
this.uuidSubscription?.unsubscribe();
|
||||
}
|
||||
|
||||
/**
|
||||
* Init
|
||||
*/
|
||||
@@ -115,7 +141,7 @@ export class AddEventActionChipComponent implements OnInit {
|
||||
this.item.type === SCThingType.DateSeries
|
||||
? Promise.resolve([this.item as SCDateSeries])
|
||||
: this.dataProvider
|
||||
.search({
|
||||
.coordinatedSearch({
|
||||
filter: {
|
||||
arguments: {
|
||||
filters: [
|
||||
@@ -140,19 +166,36 @@ export class AddEventActionChipComponent implements OnInit {
|
||||
},
|
||||
})
|
||||
.then(it => it.data as SCDateSeries[]);
|
||||
this.associatedDateSeries.then(it =>
|
||||
this.applyState(
|
||||
it.length === 0
|
||||
? AddEventStates.UNAVAILABLE
|
||||
: AddEventStates.REMOVED_ALL,
|
||||
),
|
||||
|
||||
this.uuidSubscription = this.scheduleProvider.uuids$.subscribe(
|
||||
async result => {
|
||||
this.uuids = result;
|
||||
const associatedDateSeries = await this.associatedDateSeries;
|
||||
if (associatedDateSeries.length === 0) {
|
||||
this.applyState(AddEventStates.UNAVAILABLE);
|
||||
|
||||
return;
|
||||
}
|
||||
switch (
|
||||
difference(map(associatedDateSeries, 'uid'), this.uuids).length
|
||||
) {
|
||||
case 0:
|
||||
this.applyState(AddEventStates.ADDED_ALL);
|
||||
break;
|
||||
case associatedDateSeries.length:
|
||||
this.applyState(AddEventStates.REMOVED_ALL);
|
||||
break;
|
||||
default:
|
||||
this.applyState(AddEventStates.ADDED_SOME);
|
||||
break;
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Action
|
||||
*/
|
||||
// @Override
|
||||
async onClick(event: MouseEvent) {
|
||||
const associatedDateSeries = await this.associatedDateSeries;
|
||||
const popover = await this.popoverController.create({
|
||||
@@ -165,12 +208,5 @@ export class AddEventActionChipComponent implements OnInit {
|
||||
event: event,
|
||||
});
|
||||
await popover.present();
|
||||
// TODO: replace dummy implementation
|
||||
await popover.onDidDismiss();
|
||||
this.applyState(
|
||||
this.state === AddEventStates.ADDED_ALL
|
||||
? AddEventStates.REMOVED_ALL
|
||||
: AddEventStates.ADDED_ALL,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
<div *ngIf="associatedDateSeries | async as associatedDateSeries; else loading">
|
||||
<div class="stack-children">
|
||||
<ion-chip
|
||||
*ngIf="associatedDateSeries | async as associatedDateSeries; else loading"
|
||||
@chipTransition
|
||||
[disabled]="disabled"
|
||||
(click)="$event.stopPropagation(); onClick($event)"
|
||||
>
|
||||
<ion-icon [name]="icon"></ion-icon>
|
||||
<ion-label>{{ label | translate }}</ion-label>
|
||||
</ion-chip>
|
||||
<ng-template #loading>
|
||||
<ion-chip @chipSkeletonTransition>
|
||||
<ion-skeleton-text animated="true"></ion-skeleton-text>
|
||||
</ion-chip>
|
||||
</ng-template>
|
||||
</div>
|
||||
<ng-template #loading>
|
||||
<ion-chip>
|
||||
<ion-skeleton-text animated="true"></ion-skeleton-text>
|
||||
</ion-chip>
|
||||
</ng-template>
|
||||
|
||||
@@ -1,3 +1,14 @@
|
||||
::ng-deep ion-skeleton-text {
|
||||
:host ::ng-deep ion-skeleton-text {
|
||||
width: 50px;
|
||||
}
|
||||
|
||||
.stack-children {
|
||||
display: grid;
|
||||
align-items: start;
|
||||
justify-items: start;
|
||||
}
|
||||
|
||||
.stack-children > * {
|
||||
grid-column-start: 1;
|
||||
grid-row-start: 1;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user