mirror of
https://gitlab.com/openstapps/openstapps.git
synced 2026-01-20 16:42:56 +00:00
refactor: move event select popup to a modal
This commit is contained in:
committed by
Rainer Killinger
parent
8a04a43903
commit
4a3f79ca20
@@ -71,5 +71,6 @@ const catalogRoutes: Routes = [
|
||||
UtilModule,
|
||||
],
|
||||
providers: [SettingsProvider, TranslatePipe],
|
||||
exports: [EditModalComponent],
|
||||
})
|
||||
export class DashboardModule {}
|
||||
|
||||
@@ -21,7 +21,7 @@ import {EditModalItem, EditModalTypeEnum} from './edit-modal-type.enum';
|
||||
* Shows a modal window to sort and enable/disable menu items
|
||||
*/
|
||||
@Component({
|
||||
selector: 'stapps-edit-modal',
|
||||
selector: 'stapps-dashboard-edit-modal',
|
||||
templateUrl: 'edit-modal.component.html',
|
||||
styleUrls: ['edit-modal.component.scss'],
|
||||
})
|
||||
|
||||
@@ -1,286 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2022 StApps
|
||||
* This program is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the Free
|
||||
* Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import {
|
||||
ChangeDetectorRef,
|
||||
Component,
|
||||
Input,
|
||||
OnDestroy,
|
||||
OnInit,
|
||||
} from '@angular/core';
|
||||
import {ModalController, PopoverController} from '@ionic/angular';
|
||||
import {SCDateSeries} from '@openstapps/core';
|
||||
import {Subscription} from 'rxjs';
|
||||
import {
|
||||
DateSeriesRelevantData,
|
||||
ScheduleProvider,
|
||||
toDateSeriesRelevantData,
|
||||
} from '../../calendar/schedule.provider';
|
||||
import {CalendarService} from '../../calendar/calendar.service';
|
||||
import {AddEventReviewModalComponent} from '../../calendar/add-event-review-modal.component';
|
||||
import {ThingTranslatePipe} from '../../../translation/thing-translate.pipe';
|
||||
import {groupBy, groupByProperty} from '../../../_helpers/collections/group-by';
|
||||
import {mapValues} from '../../../_helpers/collections/map-values';
|
||||
import {stringSortBy} from '../../../_helpers/collections/string-sort';
|
||||
import {uniqBy} from '../../../_helpers/collections/uniq';
|
||||
import {differenceBy} from '../../../_helpers/collections/difference';
|
||||
|
||||
enum Selection {
|
||||
ON = 2,
|
||||
PARTIAL = 1,
|
||||
OFF = 1,
|
||||
}
|
||||
|
||||
/**
|
||||
* A tree
|
||||
*
|
||||
* The generic is to preserve type safety of how deep the tree goes.
|
||||
*/
|
||||
class TreeNode<T extends TreeNode<any> | SelectionValue> {
|
||||
/**
|
||||
* Value of this node
|
||||
*/
|
||||
checked: boolean;
|
||||
|
||||
/**
|
||||
* If items are partially selected
|
||||
*/
|
||||
indeterminate: boolean;
|
||||
|
||||
/**
|
||||
* Parent of this node
|
||||
*/
|
||||
parent?: TreeNode<TreeNode<T>>;
|
||||
|
||||
constructor(readonly children: T[], readonly ref: ChangeDetectorRef) {
|
||||
this.updateParents();
|
||||
this.accumulateApplyValues();
|
||||
}
|
||||
|
||||
/**
|
||||
* Accumulate values of children to set current value
|
||||
*/
|
||||
private accumulateApplyValues() {
|
||||
const selections: number[] = this.children.map(it =>
|
||||
it instanceof TreeNode
|
||||
? it.checked
|
||||
? Selection.ON
|
||||
: it.indeterminate
|
||||
? Selection.PARTIAL
|
||||
: Selection.OFF
|
||||
: (it as SelectionValue).selected
|
||||
? Selection.ON
|
||||
: Selection.OFF,
|
||||
);
|
||||
|
||||
this.checked = selections.every(it => it === Selection.ON);
|
||||
this.indeterminate = this.checked
|
||||
? false
|
||||
: selections.some(it => it > Selection.OFF);
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply the value of this node to all child nodes
|
||||
*/
|
||||
private applyValueDownwards() {
|
||||
for (const child of this.children) {
|
||||
if (child instanceof TreeNode) {
|
||||
child.checked = this.checked;
|
||||
child.indeterminate = false;
|
||||
// tslint:disable-next-line:no-any
|
||||
(child as TreeNode<any>).applyValueDownwards();
|
||||
} else {
|
||||
(child as SelectionValue).selected = this.checked;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set all children's parent to this
|
||||
*/
|
||||
private updateParents() {
|
||||
for (const child of this.children) {
|
||||
if (child instanceof TreeNode) {
|
||||
child.parent = this as TreeNode<TreeNode<T>>;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update values to all parents upwards
|
||||
*/
|
||||
private updateValueUpwards() {
|
||||
this.parent?.accumulateApplyValues();
|
||||
this.parent?.updateValueUpwards();
|
||||
}
|
||||
|
||||
/**
|
||||
* Click on this node
|
||||
*/
|
||||
click() {
|
||||
this.checked = !this.checked;
|
||||
this.indeterminate = false;
|
||||
this.applyValueDownwards();
|
||||
this.updateValueUpwards();
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify that a child's value has changed
|
||||
*/
|
||||
notifyChildChanged() {
|
||||
this.accumulateApplyValues();
|
||||
this.updateValueUpwards();
|
||||
}
|
||||
}
|
||||
|
||||
interface SelectionValue {
|
||||
/**
|
||||
* Item that was selected
|
||||
*/
|
||||
item: SCDateSeries;
|
||||
|
||||
/**
|
||||
* Selection
|
||||
*/
|
||||
selected: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows a horizontal list of action chips
|
||||
*/
|
||||
@Component({
|
||||
selector: 'stapps-add-event-popover-component',
|
||||
templateUrl: 'add-event-popover.html',
|
||||
styleUrls: ['add-event-popover.scss'],
|
||||
})
|
||||
export class AddEventPopoverComponent implements OnInit, OnDestroy {
|
||||
/**
|
||||
* The item the action belongs to
|
||||
*/
|
||||
@Input() items: SCDateSeries[];
|
||||
|
||||
/**
|
||||
* Selection of the item
|
||||
*/
|
||||
selection: TreeNode<TreeNode<SelectionValue>>;
|
||||
|
||||
/**
|
||||
* Uuids
|
||||
*/
|
||||
partialDateSeries: DateSeriesRelevantData[];
|
||||
|
||||
/**
|
||||
* Uuid Subscription
|
||||
*/
|
||||
uuidSubscription: Subscription;
|
||||
|
||||
constructor(
|
||||
readonly ref: ChangeDetectorRef,
|
||||
readonly scheduleProvider: ScheduleProvider,
|
||||
readonly popoverController: PopoverController,
|
||||
readonly calendar: CalendarService,
|
||||
readonly modalController: ModalController,
|
||||
readonly thingTranslatePipe: ThingTranslatePipe,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Destroy
|
||||
*/
|
||||
ngOnDestroy() {
|
||||
this.uuidSubscription.unsubscribe();
|
||||
}
|
||||
|
||||
/**
|
||||
* Init
|
||||
*/
|
||||
ngOnInit() {
|
||||
this.uuidSubscription = this.scheduleProvider.partialEvents$.subscribe(
|
||||
async result => {
|
||||
this.partialDateSeries = result;
|
||||
|
||||
this.selection = new TreeNode(
|
||||
Object.values(
|
||||
groupBy(
|
||||
this.items
|
||||
.map(item => ({
|
||||
selected: this.partialDateSeries.some(
|
||||
it => it.uid === item.uid,
|
||||
),
|
||||
item: item,
|
||||
}))
|
||||
.sort(stringSortBy(it => it.item.repeatFrequency)),
|
||||
it => it.item.repeatFrequency,
|
||||
),
|
||||
).map(item => new TreeNode(item, this.ref)),
|
||||
this.ref,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
getSelection(): {
|
||||
selected: DateSeriesRelevantData[];
|
||||
unselected: DateSeriesRelevantData[];
|
||||
} {
|
||||
const selection = mapValues(
|
||||
groupByProperty(
|
||||
this.selection.children.flatMap(it => it.children),
|
||||
'selected',
|
||||
),
|
||||
value => value.map(it => toDateSeriesRelevantData(it.item)),
|
||||
);
|
||||
|
||||
return {selected: selection.true ?? [], unselected: selection.false ?? []};
|
||||
}
|
||||
|
||||
async export() {
|
||||
const modal = await this.modalController.create({
|
||||
component: AddEventReviewModalComponent,
|
||||
canDismiss: true,
|
||||
cssClass: 'add-modal',
|
||||
componentProps: {
|
||||
dismissAction: () => {
|
||||
modal.dismiss();
|
||||
},
|
||||
dateSeries: this.items,
|
||||
},
|
||||
});
|
||||
|
||||
await modal.present();
|
||||
await modal.onWillDismiss();
|
||||
}
|
||||
|
||||
/**
|
||||
* On selection change
|
||||
*/
|
||||
async onCommit(save: boolean) {
|
||||
if (save) {
|
||||
const {selected, unselected} = this.getSelection();
|
||||
|
||||
this.scheduleProvider.partialEvents$.next(
|
||||
uniqBy(
|
||||
[
|
||||
...differenceBy(this.partialDateSeries, unselected, it => it.uid),
|
||||
...selected,
|
||||
],
|
||||
it => it.uid,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
await this.popoverController.dismiss();
|
||||
}
|
||||
}
|
||||
@@ -1,91 +0,0 @@
|
||||
<!--
|
||||
~ Copyright (C) 2022 StApps
|
||||
~ This program is free software: you can redistribute it and/or modify it
|
||||
~ under the terms of the GNU General Public License as published by the Free
|
||||
~ Software Foundation, version 3.
|
||||
~
|
||||
~ This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
~ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
~ FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
~ more details.
|
||||
~
|
||||
~ You should have received a copy of the GNU General Public License along with
|
||||
~ this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
-->
|
||||
|
||||
<ion-card-content>
|
||||
<ion-item-group>
|
||||
<ion-item-divider (click)="selection.click()">
|
||||
<ion-label>{{
|
||||
'data.chips.add_events.popover.ALL' | translate
|
||||
}}</ion-label>
|
||||
<ion-checkbox
|
||||
slot="start"
|
||||
[checked]="selection.checked"
|
||||
[indeterminate]="selection.indeterminate"
|
||||
>
|
||||
</ion-checkbox>
|
||||
</ion-item-divider>
|
||||
<ion-item-group *ngFor="let frequency of selection.children">
|
||||
<ion-item-divider (click)="frequency.click()">
|
||||
<ion-label class="ion-text-wrap">{{
|
||||
frequency.children[0].item.repeatFrequency
|
||||
? (frequency.children[0].item.repeatFrequency
|
||||
| durationLocalized: true
|
||||
| sentencecase)
|
||||
: ('data.chips.add_events.popover.SINGLE' | translate | titlecase)
|
||||
}}</ion-label>
|
||||
<ion-checkbox
|
||||
slot="start"
|
||||
[checked]="frequency.checked"
|
||||
[indeterminate]="frequency.indeterminate"
|
||||
>
|
||||
</ion-checkbox>
|
||||
</ion-item-divider>
|
||||
<ion-item
|
||||
*ngFor="let date of frequency.children"
|
||||
(click)="date.selected = !date.selected; frequency.notifyChildChanged()"
|
||||
>
|
||||
<ion-label
|
||||
class="ion-text-wrap"
|
||||
*ngIf="date.item.dates.length > 1; else single_event"
|
||||
>
|
||||
{{ date.item.duration | amDuration: 'hours' }}
|
||||
{{ 'data.chips.add_events.popover.AT' | translate }}
|
||||
{{ date.item.dates[0] | amDateFormat: 'HH:mm ddd' }}
|
||||
{{ 'data.chips.add_events.popover.UNTIL' | translate }}
|
||||
{{ date.item.dates[date.item.dates.length - 1] | amDateFormat: 'll' }}
|
||||
</ion-label>
|
||||
<ng-template #single_event>
|
||||
<ion-label class="ion-text-wrap">
|
||||
{{ date.item.duration | amDuration: 'hours' }}
|
||||
{{ 'data.chips.add_events.popover.AT' | translate }}
|
||||
{{
|
||||
date.item.dates[date.item.dates.length - 1]
|
||||
| amDateFormat: 'll, HH:mm'
|
||||
}}
|
||||
</ion-label>
|
||||
</ng-template>
|
||||
<ion-checkbox slot="start" [checked]="date.selected"> </ion-checkbox>
|
||||
</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>
|
||||
<div class="download-button">
|
||||
<ion-button
|
||||
fill="clear"
|
||||
(click)="export()"
|
||||
[disabled]="!(selection.indeterminate || selection.checked)"
|
||||
>
|
||||
<ion-icon slot="icon-only" name="download"></ion-icon>
|
||||
<!-- {{ 'export' | translate }} -->
|
||||
</ion-button>
|
||||
</div>
|
||||
</ion-card-content>
|
||||
@@ -1,26 +0,0 @@
|
||||
/*!
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
::ng-deep ion-item-divider {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.action-buttons {
|
||||
float: right;
|
||||
}
|
||||
|
||||
.download-button {
|
||||
float: left;
|
||||
}
|
||||
@@ -1,25 +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 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.
|
||||
* 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/>.
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/* tslint:disable:prefer-function-over-method */
|
||||
import {Component, Input, OnDestroy} from '@angular/core';
|
||||
import {PopoverController} from '@ionic/angular';
|
||||
import {Component, Input, OnDestroy, ViewChild} from '@angular/core';
|
||||
import {IonRouterOutlet, ModalController} from '@ionic/angular';
|
||||
import {SCDateSeries, SCThing, SCThingType, SCUuid} from '@openstapps/core';
|
||||
import {Subscription} from 'rxjs';
|
||||
import {ScheduleProvider} from '../../../calendar/schedule.provider';
|
||||
import {AddEventPopoverComponent} from '../add-event-popover.component';
|
||||
import {CoordinatedSearchProvider} from '../../coordinated-search.provider';
|
||||
import {
|
||||
chipSkeletonTransition,
|
||||
@@ -29,6 +28,8 @@ import {
|
||||
AddEventStates,
|
||||
AddEventStatesMap,
|
||||
} from './add-event-action-chip.config';
|
||||
import {EditEventSelectionComponent} from '../edit-event-selection.component';
|
||||
import {AddEventReviewModalComponent} from '../../../calendar/add-event-review-modal.component';
|
||||
|
||||
/**
|
||||
* Shows a horizontal list of action chips
|
||||
@@ -82,10 +83,14 @@ export class AddEventActionChipComponent implements OnDestroy {
|
||||
*/
|
||||
uuidSubscription: Subscription;
|
||||
|
||||
@ViewChild('selection', {static: false})
|
||||
selection: EditEventSelectionComponent;
|
||||
|
||||
constructor(
|
||||
readonly popoverController: PopoverController,
|
||||
readonly dataProvider: CoordinatedSearchProvider,
|
||||
readonly modalController: ModalController,
|
||||
readonly scheduleProvider: ScheduleProvider,
|
||||
readonly routerOutlet: IonRouterOutlet,
|
||||
) {}
|
||||
|
||||
/**
|
||||
@@ -107,6 +112,24 @@ export class AddEventActionChipComponent implements OnDestroy {
|
||||
this.uuidSubscription?.unsubscribe();
|
||||
}
|
||||
|
||||
async export() {
|
||||
const modal = await this.modalController.create({
|
||||
component: AddEventReviewModalComponent,
|
||||
canDismiss: true,
|
||||
cssClass: 'add-modal',
|
||||
presentingElement: await this.modalController.getTop(),
|
||||
componentProps: {
|
||||
dismissAction: () => {
|
||||
modal.dismiss();
|
||||
},
|
||||
dateSeries: this.selection.items,
|
||||
},
|
||||
});
|
||||
|
||||
await modal.present();
|
||||
await modal.onWillDismiss();
|
||||
}
|
||||
|
||||
/**
|
||||
* Init
|
||||
*/
|
||||
@@ -176,21 +199,4 @@ export class AddEventActionChipComponent implements OnDestroy {
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Action
|
||||
*/
|
||||
async onClick(event: MouseEvent) {
|
||||
const associatedDateSeries = await this.associatedDateSeries;
|
||||
const popover = await this.popoverController.create({
|
||||
component: AddEventPopoverComponent,
|
||||
translucent: true,
|
||||
cssClass: 'add-event-popover',
|
||||
componentProps: {
|
||||
items: associatedDateSeries,
|
||||
},
|
||||
event: event,
|
||||
});
|
||||
await popover.present();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
<!--
|
||||
~ 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 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.
|
||||
~ 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/>.
|
||||
~ 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="stack-children">
|
||||
@@ -18,10 +18,45 @@
|
||||
*ngIf="associatedDateSeries | async as associatedDateSeries; else loading"
|
||||
@chipTransition
|
||||
[disabled]="disabled"
|
||||
(click)="$event.stopPropagation(); onClick($event)"
|
||||
(click)="$event.stopPropagation(); editModal.present()"
|
||||
>
|
||||
<ion-icon [name]="icon" [fill]="iconFill"></ion-icon>
|
||||
<ion-label>{{ label | translate }}</ion-label>
|
||||
<stapps-edit-modal #editModal (save)="selection.save()">
|
||||
<ng-template>
|
||||
<ion-content class="ion-padding modal-content">
|
||||
<div>
|
||||
<stapps-edit-event-selection
|
||||
#selection
|
||||
[items]="associatedDateSeries"
|
||||
(modified)="editModal.pendingChanges = true"
|
||||
></stapps-edit-event-selection>
|
||||
</div>
|
||||
</ion-content>
|
||||
<ion-footer mode="ios">
|
||||
<ion-toolbar color="light">
|
||||
<ion-button
|
||||
slot="end"
|
||||
fill="clear"
|
||||
(click)="export()"
|
||||
[disabled]="
|
||||
!(
|
||||
selection.selection.indeterminate ||
|
||||
selection.selection.checked
|
||||
)
|
||||
"
|
||||
>
|
||||
{{
|
||||
'schedule.toCalendar.reviewModal.DOWNLOAD'
|
||||
| translate
|
||||
| titlecase
|
||||
}}
|
||||
<ion-icon slot="end" name="download"></ion-icon>
|
||||
</ion-button>
|
||||
</ion-toolbar>
|
||||
</ion-footer>
|
||||
</ng-template>
|
||||
</stapps-edit-modal>
|
||||
</ion-chip>
|
||||
<ng-template #loading>
|
||||
<ion-chip @chipSkeletonTransition>
|
||||
|
||||
@@ -1,3 +1,19 @@
|
||||
/*!
|
||||
* 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 "src/theme/common/ion-content-parallax";
|
||||
|
||||
:host {
|
||||
display: block;
|
||||
padding: var(--spacing-sm);
|
||||
@@ -17,3 +33,14 @@
|
||||
grid-column-start: 1;
|
||||
grid-row-start: 1;
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
--background: var(--ion-color-primary);
|
||||
--color: var(--ion-color-primary-contrast);
|
||||
|
||||
@include ion-content-parallax($content-size: 160px)
|
||||
}
|
||||
|
||||
ion-footer > ion-toolbar {
|
||||
--border-color: var(--ion-color-light-shade);
|
||||
}
|
||||
|
||||
130
src/app/modules/data/chips/edit-event-selection.component.ts
Normal file
130
src/app/modules/data/chips/edit-event-selection.component.ts
Normal file
@@ -0,0 +1,130 @@
|
||||
/*
|
||||
* 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 {
|
||||
ChangeDetectorRef,
|
||||
Component,
|
||||
EventEmitter,
|
||||
Input,
|
||||
OnInit,
|
||||
Output,
|
||||
} from '@angular/core';
|
||||
import {ModalController, PopoverController} from '@ionic/angular';
|
||||
import {SCDateSeries} from '@openstapps/core';
|
||||
import {
|
||||
DateSeriesRelevantData,
|
||||
ScheduleProvider,
|
||||
toDateSeriesRelevantData,
|
||||
} from '../../calendar/schedule.provider';
|
||||
import {CalendarService} from '../../calendar/calendar.service';
|
||||
import {ThingTranslatePipe} from '../../../translation/thing-translate.pipe';
|
||||
import {groupBy, groupByProperty} from '../../../_helpers/collections/group-by';
|
||||
import {mapValues} from '../../../_helpers/collections/map-values';
|
||||
import {stringSortBy} from '../../../_helpers/collections/string-sort';
|
||||
import {uniqBy} from '../../../_helpers/collections/uniq';
|
||||
import {differenceBy} from '../../../_helpers/collections/difference';
|
||||
import {SelectionValue, TreeNode} from './tree-node';
|
||||
|
||||
/**
|
||||
* Shows a horizontal list of action chips
|
||||
*/
|
||||
@Component({
|
||||
selector: 'stapps-edit-event-selection',
|
||||
templateUrl: 'edit-event-selection.html',
|
||||
styleUrls: ['edit-event-selection.scss'],
|
||||
})
|
||||
export class EditEventSelectionComponent implements OnInit {
|
||||
/**
|
||||
* The item the action belongs to
|
||||
*/
|
||||
@Input() items: SCDateSeries[];
|
||||
|
||||
/**
|
||||
* Selection of the item
|
||||
*/
|
||||
selection: TreeNode<TreeNode<SelectionValue>>;
|
||||
|
||||
/**
|
||||
* Uuids
|
||||
*/
|
||||
partialDateSeries: DateSeriesRelevantData[];
|
||||
|
||||
@Output()
|
||||
modified = new EventEmitter();
|
||||
|
||||
constructor(
|
||||
readonly ref: ChangeDetectorRef,
|
||||
readonly scheduleProvider: ScheduleProvider,
|
||||
readonly popoverController: PopoverController,
|
||||
readonly calendar: CalendarService,
|
||||
readonly modalController: ModalController,
|
||||
readonly thingTranslatePipe: ThingTranslatePipe,
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
this.partialDateSeries = this.scheduleProvider.partialEvents$.value;
|
||||
this.reset();
|
||||
}
|
||||
|
||||
private getSelection(): {
|
||||
selected: DateSeriesRelevantData[];
|
||||
unselected: DateSeriesRelevantData[];
|
||||
} {
|
||||
const selection = mapValues(
|
||||
groupByProperty(
|
||||
this.selection.children.flatMap(it => it.children),
|
||||
'selected',
|
||||
),
|
||||
value => value.map(it => toDateSeriesRelevantData(it.item)),
|
||||
);
|
||||
|
||||
return {selected: selection.true ?? [], unselected: selection.false ?? []};
|
||||
}
|
||||
|
||||
getModifiedEvents(): DateSeriesRelevantData[] {
|
||||
const {selected, unselected} = this.getSelection();
|
||||
|
||||
return uniqBy(
|
||||
[
|
||||
...differenceBy(this.partialDateSeries, unselected, it => it.uid),
|
||||
...selected,
|
||||
],
|
||||
it => it.uid,
|
||||
);
|
||||
}
|
||||
|
||||
reset() {
|
||||
this.selection = new TreeNode(
|
||||
Object.values(
|
||||
groupBy(
|
||||
this.items
|
||||
.map(item => ({
|
||||
selected: this.partialDateSeries.some(it => it.uid === item.uid),
|
||||
item: item,
|
||||
}))
|
||||
.sort(stringSortBy(it => it.item.repeatFrequency)),
|
||||
it => it.item.repeatFrequency,
|
||||
),
|
||||
).map(item => new TreeNode(item, this.ref)),
|
||||
this.ref,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Save selection
|
||||
*/
|
||||
save() {
|
||||
this.scheduleProvider.partialEvents$.next(this.getModifiedEvents());
|
||||
}
|
||||
}
|
||||
86
src/app/modules/data/chips/edit-event-selection.html
Normal file
86
src/app/modules/data/chips/edit-event-selection.html
Normal file
@@ -0,0 +1,86 @@
|
||||
<!--
|
||||
~ 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-item
|
||||
(click)="modified.emit(); selection.click()"
|
||||
class="list-header"
|
||||
lines="none"
|
||||
>
|
||||
<ion-list-header>
|
||||
<ion-label>{{ 'data.chips.add_events.popover.ALL' | translate }}</ion-label>
|
||||
</ion-list-header>
|
||||
<ion-checkbox
|
||||
slot="end"
|
||||
[checked]="selection.checked"
|
||||
[indeterminate]="selection.indeterminate"
|
||||
>
|
||||
</ion-checkbox>
|
||||
</ion-item>
|
||||
<ng-container *ngFor="let frequency of selection.children">
|
||||
<ion-list inset="true" lines="full">
|
||||
<ion-item
|
||||
lines="none"
|
||||
(click)="modified.emit(); frequency.click()"
|
||||
class="list-header"
|
||||
>
|
||||
<ion-list-header>
|
||||
<ion-label>{{
|
||||
frequency.children[0].item.repeatFrequency
|
||||
? (frequency.children[0].item.repeatFrequency
|
||||
| durationLocalized: true
|
||||
| sentencecase)
|
||||
: ('data.chips.add_events.popover.SINGLE' | translate | titlecase)
|
||||
}}</ion-label>
|
||||
<ion-button></ion-button>
|
||||
</ion-list-header>
|
||||
<ion-checkbox
|
||||
slot="end"
|
||||
[checked]="frequency.checked"
|
||||
[indeterminate]="frequency.indeterminate"
|
||||
>
|
||||
</ion-checkbox>
|
||||
</ion-item>
|
||||
<ion-item
|
||||
*ngFor="let date of frequency.children"
|
||||
(click)="
|
||||
modified.emit();
|
||||
date.selected = !date.selected;
|
||||
frequency.notifyChildChanged()
|
||||
"
|
||||
>
|
||||
<ion-label
|
||||
*ngIf="date.item.dates.length > 1; else single_event"
|
||||
class="ion-text-wrap"
|
||||
>
|
||||
{{ date.item.duration | amDuration: 'hours' }}
|
||||
{{ 'data.chips.add_events.popover.AT' | translate }}
|
||||
{{ date.item.dates[0] | amDateFormat: 'HH:mm ddd' }}
|
||||
{{ 'data.chips.add_events.popover.UNTIL' | translate }}
|
||||
{{ date.item.dates[date.item.dates.length - 1] | amDateFormat: 'll' }}
|
||||
</ion-label>
|
||||
<ng-template #single_event>
|
||||
<ion-label class="ion-text-wrap">
|
||||
{{ date.item.duration | amDuration: 'hours' }}
|
||||
{{ 'data.chips.add_events.popover.AT' | translate }}
|
||||
{{
|
||||
date.item.dates[date.item.dates.length - 1]
|
||||
| amDateFormat: 'll, HH:mm'
|
||||
}}
|
||||
</ion-label>
|
||||
</ng-template>
|
||||
<ion-checkbox slot="end" [checked]="date.selected"> </ion-checkbox>
|
||||
</ion-item>
|
||||
</ion-list>
|
||||
</ng-container>
|
||||
45
src/app/modules/data/chips/edit-event-selection.scss
Normal file
45
src/app/modules/data/chips/edit-event-selection.scss
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/>.
|
||||
*/
|
||||
|
||||
ion-item-divider.ios > ion-checkbox {
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
|
||||
.list-header {
|
||||
--padding-start: 0;
|
||||
--background: var(--ion-color-primary-shade);
|
||||
|
||||
> ion-list-header {
|
||||
--color: var(--ion-color-primary-contrast);
|
||||
--background: none;
|
||||
}
|
||||
|
||||
> ion-checkbox {
|
||||
--background: none;
|
||||
--border-color: rgba(var(--ion-color-primary-contrast-rgb), 0.77);
|
||||
--background-checked: var(--ion-color-primary-contrast);
|
||||
--border-color-checked: var(--ion-color-primary-contrast);
|
||||
--checkmark-color: var(--ion-color-primary)
|
||||
}
|
||||
}
|
||||
|
||||
:host > .list-header {
|
||||
--background: none;
|
||||
}
|
||||
|
||||
ion-list.md {
|
||||
padding-top: 0;
|
||||
}
|
||||
138
src/app/modules/data/chips/tree-node.ts
Normal file
138
src/app/modules/data/chips/tree-node.ts
Normal file
@@ -0,0 +1,138 @@
|
||||
/*
|
||||
* 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 {ChangeDetectorRef} from '@angular/core';
|
||||
import {SCDateSeries} from '@openstapps/core';
|
||||
|
||||
export enum Selection {
|
||||
ON = 2,
|
||||
PARTIAL = 1,
|
||||
OFF = 1,
|
||||
}
|
||||
|
||||
/**
|
||||
* A tree
|
||||
*
|
||||
* The generic is to preserve type safety of how deep the tree goes.
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export class TreeNode<T extends TreeNode<any> | SelectionValue> {
|
||||
/**
|
||||
* Value of this node
|
||||
*/
|
||||
checked: boolean;
|
||||
|
||||
/**
|
||||
* If items are partially selected
|
||||
*/
|
||||
indeterminate: boolean;
|
||||
|
||||
/**
|
||||
* Parent of this node
|
||||
*/
|
||||
parent?: TreeNode<TreeNode<T>>;
|
||||
|
||||
constructor(readonly children: T[], readonly ref: ChangeDetectorRef) {
|
||||
this.updateParents();
|
||||
this.accumulateApplyValues();
|
||||
}
|
||||
|
||||
/**
|
||||
* Accumulate values of children to set current value
|
||||
*/
|
||||
private accumulateApplyValues() {
|
||||
const selections: number[] = this.children.map(it =>
|
||||
it instanceof TreeNode
|
||||
? it.checked
|
||||
? Selection.ON
|
||||
: it.indeterminate
|
||||
? Selection.PARTIAL
|
||||
: Selection.OFF
|
||||
: (it as SelectionValue).selected
|
||||
? Selection.ON
|
||||
: Selection.OFF,
|
||||
);
|
||||
|
||||
this.checked = selections.every(it => it === Selection.ON);
|
||||
this.indeterminate = this.checked
|
||||
? false
|
||||
: selections.some(it => it > Selection.OFF);
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply the value of this node to all child nodes
|
||||
*/
|
||||
private applyValueDownwards() {
|
||||
for (const child of this.children) {
|
||||
if (child instanceof TreeNode) {
|
||||
child.checked = this.checked;
|
||||
child.indeterminate = false;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
(child as TreeNode<any>).applyValueDownwards();
|
||||
} else {
|
||||
(child as SelectionValue).selected = this.checked;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set all children's parent to this
|
||||
*/
|
||||
private updateParents() {
|
||||
for (const child of this.children) {
|
||||
if (child instanceof TreeNode) {
|
||||
child.parent = this as TreeNode<TreeNode<T>>;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update values to all parents upwards
|
||||
*/
|
||||
private updateValueUpwards() {
|
||||
this.parent?.accumulateApplyValues();
|
||||
this.parent?.updateValueUpwards();
|
||||
}
|
||||
|
||||
/**
|
||||
* Click on this node
|
||||
*/
|
||||
click() {
|
||||
this.checked = !this.checked;
|
||||
this.indeterminate = false;
|
||||
this.applyValueDownwards();
|
||||
this.updateValueUpwards();
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify that a child's value has changed
|
||||
*/
|
||||
notifyChildChanged() {
|
||||
this.accumulateApplyValues();
|
||||
this.updateValueUpwards();
|
||||
}
|
||||
}
|
||||
|
||||
export interface SelectionValue {
|
||||
/**
|
||||
* Item that was selected
|
||||
*/
|
||||
item: SCDateSeries;
|
||||
|
||||
/**
|
||||
* Selection
|
||||
*/
|
||||
selected: boolean;
|
||||
}
|
||||
@@ -1,16 +1,16 @@
|
||||
/*
|
||||
* 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 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.
|
||||
* 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/>.
|
||||
* 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 {ScrollingModule} from '@angular/cdk/scrolling';
|
||||
import {CommonModule} from '@angular/common';
|
||||
@@ -26,7 +26,7 @@ import {MenuModule} from '../menu/menu.module';
|
||||
import {ScheduleProvider} from '../calendar/schedule.provider';
|
||||
import {StorageModule} from '../storage/storage.module';
|
||||
import {ActionChipListComponent} from './chips/action-chip-list.component';
|
||||
import {AddEventPopoverComponent} from './chips/add-event-popover.component';
|
||||
import {EditEventSelectionComponent} from './chips/edit-event-selection.component';
|
||||
import {AddEventActionChipComponent} from './chips/data/add-event-action-chip.component';
|
||||
import {LocateActionChipComponent} from './chips/data/locate-action-chip.component';
|
||||
import {DataFacetsProvider} from './data-facets.provider';
|
||||
@@ -99,7 +99,7 @@ import {ExternalLinkComponent} from './elements/external-link.component';
|
||||
declarations: [
|
||||
ActionChipListComponent,
|
||||
AddEventActionChipComponent,
|
||||
AddEventPopoverComponent,
|
||||
EditEventSelectionComponent,
|
||||
AddressDetailComponent,
|
||||
ArticleDetailContentComponent,
|
||||
ArticleListItemComponent,
|
||||
|
||||
97
src/app/util/edit-modal.component.ts
Normal file
97
src/app/util/edit-modal.component.ts
Normal file
@@ -0,0 +1,97 @@
|
||||
/*
|
||||
* 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,
|
||||
ContentChild,
|
||||
EventEmitter,
|
||||
Input,
|
||||
OnInit,
|
||||
Output,
|
||||
TemplateRef,
|
||||
ViewChild,
|
||||
} from '@angular/core';
|
||||
import {
|
||||
ActionSheetController,
|
||||
AlertController,
|
||||
Config,
|
||||
IonModal,
|
||||
IonRouterOutlet,
|
||||
ModalController,
|
||||
} from '@ionic/angular';
|
||||
import {
|
||||
pendingChangesActionSheet,
|
||||
PendingChangesRole,
|
||||
} from './pending-changes-action-sheet';
|
||||
import {TranslatePipe} from '@ngx-translate/core';
|
||||
|
||||
@Component({
|
||||
selector: 'stapps-edit-modal',
|
||||
templateUrl: 'edit-modal.html',
|
||||
})
|
||||
export class EditModalComponent implements OnInit {
|
||||
@ContentChild(TemplateRef) content: TemplateRef<unknown>;
|
||||
|
||||
@ViewChild('modal') modal: IonModal;
|
||||
|
||||
@Input() pendingChanges = false;
|
||||
|
||||
@Output() save = new EventEmitter();
|
||||
|
||||
presentingElement: HTMLElement;
|
||||
|
||||
constructor(
|
||||
readonly modalController: ModalController,
|
||||
readonly routerOutlet: IonRouterOutlet,
|
||||
readonly alertController: AlertController,
|
||||
readonly actionSheetController: ActionSheetController,
|
||||
readonly translatePipe: TranslatePipe,
|
||||
readonly config: Config,
|
||||
) {}
|
||||
|
||||
async ngOnInit() {
|
||||
this.presentingElement =
|
||||
(await this.modalController.getTop()) || this.routerOutlet.nativeEl;
|
||||
}
|
||||
|
||||
present() {
|
||||
this.modal.present();
|
||||
this.pendingChanges = false;
|
||||
}
|
||||
|
||||
dismiss(skipChanges = false) {
|
||||
this.pendingChanges = skipChanges ? false : this.pendingChanges;
|
||||
setTimeout(() => this.modal.dismiss());
|
||||
}
|
||||
|
||||
canDismissModal = async () => {
|
||||
const alert =
|
||||
this.config.get('mode') === 'ios'
|
||||
? await this.actionSheetController.create(
|
||||
pendingChangesActionSheet(this.translatePipe),
|
||||
)
|
||||
: await this.alertController.create(
|
||||
pendingChangesActionSheet(this.translatePipe, false),
|
||||
);
|
||||
alert.present().then();
|
||||
|
||||
const {role} = await alert.onWillDismiss();
|
||||
if (role === PendingChangesRole.SAVE) {
|
||||
this.save.emit();
|
||||
}
|
||||
|
||||
return role !== 'backdrop' && role !== PendingChangesRole.CANCEL;
|
||||
};
|
||||
}
|
||||
39
src/app/util/edit-modal.html
Normal file
39
src/app/util/edit-modal.html
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/>.
|
||||
-->
|
||||
|
||||
<ion-modal
|
||||
#modal
|
||||
[presentingElement]="presentingElement"
|
||||
[canDismiss]="!pendingChanges || canDismissModal"
|
||||
>
|
||||
<ng-template>
|
||||
<ion-header mode="ios">
|
||||
<ion-toolbar>
|
||||
<ion-title>{{ 'modal.TITLE_EDIT' | translate }}</ion-title>
|
||||
<ion-buttons slot="end">
|
||||
<ion-button (click)="save.emit(); dismiss(true)">{{
|
||||
'modal.DISMISS_CONFIRM' | translate
|
||||
}}</ion-button>
|
||||
</ion-buttons>
|
||||
<ion-buttons slot="start">
|
||||
<ion-button (click)="dismiss(true)">{{
|
||||
'modal.DISMISS_CANCEL' | translate
|
||||
}}</ion-button>
|
||||
</ion-buttons>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
<ng-container *ngTemplateOutlet="content"> </ng-container>
|
||||
</ng-template>
|
||||
</ion-modal>
|
||||
59
src/app/util/pending-changes-action-sheet.ts
Normal file
59
src/app/util/pending-changes-action-sheet.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* 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 {TranslatePipe} from '@ngx-translate/core';
|
||||
import {ActionSheetOptions, AlertOptions} from '@ionic/angular';
|
||||
|
||||
export enum PendingChangesRole {
|
||||
SAVE = 'save',
|
||||
DISCARD = 'discard',
|
||||
CANCEL = 'cancel',
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
export function pendingChangesActionSheet(
|
||||
translatePipe: TranslatePipe,
|
||||
includeSaveOption = true,
|
||||
): ActionSheetOptions & AlertOptions {
|
||||
return {
|
||||
header: translatePipe.transform('modal.dismiss_warn_pending_changes.TITLE'),
|
||||
buttons: [
|
||||
...(includeSaveOption
|
||||
? [
|
||||
{
|
||||
text: translatePipe.transform(
|
||||
'modal.dismiss_warn_pending_changes.SAVE',
|
||||
),
|
||||
role: PendingChangesRole.SAVE,
|
||||
},
|
||||
]
|
||||
: []),
|
||||
{
|
||||
text: translatePipe.transform(
|
||||
'modal.dismiss_warn_pending_changes.CANCEL',
|
||||
),
|
||||
role: PendingChangesRole.CANCEL,
|
||||
},
|
||||
{
|
||||
text: translatePipe.transform(
|
||||
'modal.dismiss_warn_pending_changes.DISCARD',
|
||||
),
|
||||
role: PendingChangesRole.DISCARD,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
@@ -1,16 +1,16 @@
|
||||
/*
|
||||
* 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 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.
|
||||
* 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/>.
|
||||
* 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';
|
||||
@@ -21,8 +21,13 @@ import {DateFromIndexPipe} from './date-from-index.pipe';
|
||||
import {DaytimeKeyPipe} from './daytime-key.pipe';
|
||||
import {LazyPipe} from './lazy.pipe';
|
||||
import {NextDateInListPipe} from './next-date-in-list.pipe';
|
||||
import {EditModalComponent} from './edit-modal.component';
|
||||
import {BrowserModule} from '@angular/platform-browser';
|
||||
import {IonicModule} from '@ionic/angular';
|
||||
import {TranslateModule} from '@ngx-translate/core';
|
||||
|
||||
@NgModule({
|
||||
imports: [BrowserModule, IonicModule, TranslateModule],
|
||||
declarations: [
|
||||
ArrayLastPipe,
|
||||
DateIsThisPipe,
|
||||
@@ -31,6 +36,7 @@ import {NextDateInListPipe} from './next-date-in-list.pipe';
|
||||
DateFromIndexPipe,
|
||||
DaytimeKeyPipe,
|
||||
NextDateInListPipe,
|
||||
EditModalComponent,
|
||||
],
|
||||
exports: [
|
||||
ArrayLastPipe,
|
||||
@@ -40,6 +46,7 @@ import {NextDateInListPipe} from './next-date-in-list.pipe';
|
||||
DateFromIndexPipe,
|
||||
DaytimeKeyPipe,
|
||||
NextDateInListPipe,
|
||||
EditModalComponent,
|
||||
],
|
||||
})
|
||||
export class UtilModule {}
|
||||
|
||||
@@ -11,6 +11,13 @@
|
||||
"DISMISS_CANCEL": "Abbrechen",
|
||||
"DISMISS_CONFIRM": "Bestätigen",
|
||||
"DISMISS": "Schließen",
|
||||
"TITLE_EDIT": "Bearbeiten",
|
||||
"dismiss_warn_pending_changes": {
|
||||
"TITLE": "Ausstehende Änderungen",
|
||||
"SAVE": "Speichern",
|
||||
"DISCARD": "Verwerfen",
|
||||
"CANCEL": "Abbrechen"
|
||||
},
|
||||
"settings": "Einstellungen"
|
||||
},
|
||||
"app": {
|
||||
|
||||
@@ -11,6 +11,13 @@
|
||||
"DISMISS_CANCEL": "Cancel",
|
||||
"DISMISS_CONFIRM": "Confirm",
|
||||
"DISMISS": "Close",
|
||||
"TITLE_EDIT": "Edit",
|
||||
"dismiss_warn_pending_changes": {
|
||||
"TITLE": "Pending changes",
|
||||
"SAVE": "Save",
|
||||
"DISCARD": "Discard",
|
||||
"CANCEL": "Cancel"
|
||||
},
|
||||
"settings": "Settings"
|
||||
},
|
||||
"app": {
|
||||
|
||||
@@ -17,14 +17,15 @@
|
||||
$parallax-background: var(--ion-color-primary),
|
||||
$background: var(--ion-color-light),
|
||||
$parallax-strength: 2,
|
||||
$overscroll-padding: 50vh,
|
||||
$content-size: 0px,
|
||||
$overscroll-padding: 720px,
|
||||
$content-size: 230px,
|
||||
) {
|
||||
&::part(background) {
|
||||
background: $background;
|
||||
}
|
||||
&::part(scroll) {
|
||||
perspective: 2px;
|
||||
perspective-origin: center top;
|
||||
}
|
||||
> div {
|
||||
transform-style: preserve-3d;
|
||||
@@ -35,13 +36,21 @@
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
height: calc(#{$content-size} + #{$parallax-strength} * #{$overscroll-padding});
|
||||
width: 200%;
|
||||
left: 0;
|
||||
|
||||
$height: calc($content-size + $overscroll-padding);
|
||||
$translateY: calc($overscroll-padding * $parallax-strength);
|
||||
$translateZ: calc(-1px * $parallax-strength);
|
||||
$transform-origin: calc($parallax-strength * $parallax-strength * $overscroll-padding);
|
||||
|
||||
height: $height;
|
||||
width: 150%;
|
||||
transform-origin: 50% $transform-origin;
|
||||
transform:
|
||||
translateY(calc(-#{$overscroll-padding} * #{$parallax-strength}))
|
||||
translateZ(calc(-1px * #{$parallax-strength}))
|
||||
scale(1.5);
|
||||
translate3d(0px, $translateY, $translateZ)
|
||||
scale($parallax-strength);
|
||||
z-index: -1;
|
||||
|
||||
background: $parallax-background;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user