mirror of
https://gitlab.com/openstapps/openstapps.git
synced 2026-01-20 00:23:03 +00:00
feat: add action chips to search results
This commit is contained in:
40
src/app/modules/data/chips/action-chip-list.component.ts
Normal file
40
src/app/modules/data/chips/action-chip-list.component.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* 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 {SCDateSeries, SCThings, SCThingType} from '@openstapps/core';
|
||||
|
||||
/**
|
||||
* Shows a horizontal list of action chips
|
||||
*/
|
||||
@Component({
|
||||
selector: 'stapps-action-chip-list',
|
||||
templateUrl: 'action-chip-list.html',
|
||||
styleUrls: ['action-chip-list.scss'],
|
||||
})
|
||||
export class ActionChipListComponent {
|
||||
/**
|
||||
* If chips are applicable
|
||||
*/
|
||||
applicable: Record<string, () => boolean> = {
|
||||
'locate': () => this.item.hasOwnProperty('inPlace'),
|
||||
'event': () => this.item.type === SCThingType.AcademicEvent ||
|
||||
(this.item.type === SCThingType.DateSeries && (this.item as SCDateSeries).dates.length !== 0),
|
||||
};
|
||||
|
||||
/**
|
||||
* The item the action belongs to
|
||||
*/
|
||||
@Input() item: SCThings;
|
||||
}
|
||||
4
src/app/modules/data/chips/action-chip-list.html
Normal file
4
src/app/modules/data/chips/action-chip-list.html
Normal file
@@ -0,0 +1,4 @@
|
||||
<div>
|
||||
<stapps-locate-action-chip *ngIf='applicable["locate"]()' [item]='item'></stapps-locate-action-chip>
|
||||
<stapps-add-event-action-chip *ngIf='applicable["event"]()' [item]='item'></stapps-add-event-action-chip>
|
||||
</div>
|
||||
4
src/app/modules/data/chips/action-chip-list.scss
Normal file
4
src/app/modules/data/chips/action-chip-list.scss
Normal file
@@ -0,0 +1,4 @@
|
||||
div {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
176
src/app/modules/data/chips/add-event-popover.component.ts
Normal file
176
src/app/modules/data/chips/add-event-popover.component.ts
Normal file
@@ -0,0 +1,176 @@
|
||||
/*
|
||||
* 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 {ChangeDetectorRef, Component, Input, OnInit} from '@angular/core';
|
||||
import {SCDateSeries} from '@openstapps/core';
|
||||
import {every, groupBy, some, sortBy, values} from 'lodash-es';
|
||||
import {capitalize, last} from 'lodash-es';
|
||||
|
||||
enum Selection {
|
||||
ON = 2,
|
||||
PARTIAL = 1,
|
||||
OFF = 1,
|
||||
}
|
||||
|
||||
/**
|
||||
* A tree
|
||||
*
|
||||
* The generic is to preserve type safety of how deep the tree goes.
|
||||
*/
|
||||
// tslint:disable-next-line:no-any
|
||||
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 = every(selections, it => it === Selection.ON);
|
||||
this.indeterminate = this.checked ? false : some(selections, 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 {
|
||||
/**
|
||||
* Lodash alias
|
||||
*/
|
||||
capitalize: (item: string) => string = capitalize;
|
||||
|
||||
/**
|
||||
* The item the action belongs to
|
||||
*/
|
||||
@Input() items: SCDateSeries[];
|
||||
|
||||
/**
|
||||
* Lodash alias
|
||||
*/
|
||||
last: <T>(item: T[] | null | undefined) => T | undefined = last;
|
||||
|
||||
/**
|
||||
* Selection of the item
|
||||
*/
|
||||
selection: TreeNode<TreeNode<SelectionValue>>;
|
||||
|
||||
constructor(readonly ref: ChangeDetectorRef) {
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
40
src/app/modules/data/chips/add-event-popover.html
Normal file
40
src/app/modules/data/chips/add-event-popover.html
Normal file
@@ -0,0 +1,40 @@
|
||||
<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>{{('frequency' | thingTranslate: frequency.children[0].item) | 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 *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}}
|
||||
{{last(date.item.dates) | amDateFormat: 'll'}}
|
||||
</ion-label>
|
||||
<ng-template #single_event>
|
||||
<ion-label>
|
||||
{{date.item.duration | amDuration: 'hours'}}
|
||||
{{'data.chips.add_events.popover.AT' | translate}}
|
||||
{{last(date.item.dates) | 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>
|
||||
</ion-card-content>
|
||||
7
src/app/modules/data/chips/add-event-popover.scss
Normal file
7
src/app/modules/data/chips/add-event-popover.scss
Normal file
@@ -0,0 +1,7 @@
|
||||
::ng-deep ion-item-divider {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
ion-card-content {
|
||||
width: fit-content;
|
||||
}
|
||||
@@ -0,0 +1,165 @@
|
||||
/* tslint:disable:prefer-function-over-method */
|
||||
/*
|
||||
* 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, OnInit} from '@angular/core';
|
||||
import {PopoverController} from '@ionic/angular';
|
||||
import {SCDateSeries, SCThing, SCThingType} from '@openstapps/core';
|
||||
import {DataProvider} from '../../data.provider';
|
||||
import {AddEventPopoverComponent} from '../add-event-popover.component';
|
||||
|
||||
enum AddEventStates {
|
||||
ADDED_ALL,
|
||||
ADDED_SOME,
|
||||
REMOVED_ALL,
|
||||
UNAVAILABLE,
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows a horizontal list of action chips
|
||||
*/
|
||||
@Component({
|
||||
selector: 'stapps-add-event-action-chip',
|
||||
templateUrl: 'add-event-action-chip.html',
|
||||
styleUrls: ['add-event-action-chip.scss'],
|
||||
})
|
||||
export class AddEventActionChipComponent implements OnInit {
|
||||
/**
|
||||
* Associated date series
|
||||
*/
|
||||
associatedDateSeries: Promise<SCDateSeries[]>;
|
||||
|
||||
/**
|
||||
* Disabled
|
||||
*/
|
||||
disabled: boolean;
|
||||
|
||||
/**
|
||||
* Icon
|
||||
*/
|
||||
icon: string;
|
||||
|
||||
/**
|
||||
* Item
|
||||
*/
|
||||
@Input() item: SCThing;
|
||||
|
||||
/**
|
||||
* Label
|
||||
*/
|
||||
label: string;
|
||||
|
||||
/**
|
||||
* State
|
||||
*/
|
||||
state: AddEventStates;
|
||||
|
||||
/**
|
||||
* States
|
||||
*/
|
||||
states = {
|
||||
[AddEventStates.ADDED_ALL]: {
|
||||
icon: 'events-all',
|
||||
label: 'data.chips.add_events.ADDED_ALL',
|
||||
disabled: false,
|
||||
},
|
||||
[AddEventStates.ADDED_SOME]: {
|
||||
icon: 'events-partial',
|
||||
label: 'data.chips.add_events.ADDED_SOME',
|
||||
disabled: false,
|
||||
},
|
||||
[AddEventStates.REMOVED_ALL]: {
|
||||
icon: 'events',
|
||||
label: 'data.chips.add_events.REMOVED_ALL',
|
||||
disabled: false,
|
||||
},
|
||||
[AddEventStates.UNAVAILABLE]: {
|
||||
icon: 'close',
|
||||
label: 'data.chips.add_events.UNAVAILABLE',
|
||||
disabled: true,
|
||||
},
|
||||
};
|
||||
|
||||
constructor(readonly popoverController: PopoverController,
|
||||
readonly dataProvider: DataProvider) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply state
|
||||
*/
|
||||
applyState(state: AddEventStates) {
|
||||
this.state = state;
|
||||
const {label, icon, disabled} = this.states[state];
|
||||
this.label = label;
|
||||
this.icon = icon;
|
||||
this.disabled = disabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Init
|
||||
*/
|
||||
ngOnInit() {
|
||||
this.associatedDateSeries = this.item.type === SCThingType.DateSeries ?
|
||||
Promise.resolve([this.item as SCDateSeries]) :
|
||||
this.dataProvider.search({
|
||||
filter: {
|
||||
arguments: {
|
||||
filters: [
|
||||
{
|
||||
arguments: {
|
||||
field: 'type',
|
||||
value: SCThingType.DateSeries,
|
||||
},
|
||||
type: 'value',
|
||||
},
|
||||
{
|
||||
arguments: {
|
||||
field: 'event.uid',
|
||||
value: this.item.uid,
|
||||
},
|
||||
type: 'value',
|
||||
},
|
||||
],
|
||||
operation: 'and',
|
||||
},
|
||||
type: 'boolean',
|
||||
},
|
||||
})
|
||||
.then((it) => it.data as SCDateSeries[]);
|
||||
this.associatedDateSeries.then((it) => this.applyState(
|
||||
it.length < 1 ? AddEventStates.UNAVAILABLE : AddEventStates.REMOVED_ALL));
|
||||
}
|
||||
|
||||
/**
|
||||
* Action
|
||||
*/
|
||||
// @Override
|
||||
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();
|
||||
// TODO: replace dummy implementation
|
||||
await popover.onDidDismiss();
|
||||
this.applyState(this.state === AddEventStates.ADDED_ALL ?
|
||||
AddEventStates.REMOVED_ALL : AddEventStates.ADDED_ALL);
|
||||
}
|
||||
}
|
||||
11
src/app/modules/data/chips/data/add-event-action-chip.html
Normal file
11
src/app/modules/data/chips/data/add-event-action-chip.html
Normal file
@@ -0,0 +1,11 @@
|
||||
<div *ngIf='(associatedDateSeries | async) as associatedDateSeries; else loading'>
|
||||
<ion-chip [disabled]='disabled' (click)='$event.stopPropagation(); onClick($event)'>
|
||||
<ion-icon [name]='icon'></ion-icon>
|
||||
<ion-label>{{label | translate}}</ion-label>
|
||||
</ion-chip>
|
||||
</div>
|
||||
<ng-template #loading>
|
||||
<ion-chip>
|
||||
<ion-skeleton-text animated='true' ></ion-skeleton-text>
|
||||
</ion-chip>
|
||||
</ng-template>
|
||||
@@ -0,0 +1,3 @@
|
||||
::ng-deep ion-skeleton-text {
|
||||
width: 50px;
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
/* tslint:disable:prefer-function-over-method */
|
||||
/*
|
||||
* 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 {SCThing} from '@openstapps/core';
|
||||
|
||||
/**
|
||||
* Shows a horizontal list of action chips
|
||||
*/
|
||||
@Component({
|
||||
selector: 'stapps-locate-action-chip',
|
||||
templateUrl: 'locate-action-chip.html',
|
||||
})
|
||||
export class LocateActionChipComponent {
|
||||
/**
|
||||
* Item
|
||||
*/
|
||||
@Input() item: SCThing;
|
||||
|
||||
/**
|
||||
* Click
|
||||
*/
|
||||
onClick(/*event: MouseEvent*/) {
|
||||
// TODO
|
||||
}
|
||||
}
|
||||
|
||||
7
src/app/modules/data/chips/data/locate-action-chip.html
Normal file
7
src/app/modules/data/chips/data/locate-action-chip.html
Normal file
@@ -0,0 +1,7 @@
|
||||
<ion-chip class='chip-class' (click)='$event.stopPropagation(); onClick()'>
|
||||
<ion-icon name='location'></ion-icon>
|
||||
<ion-label>{{'Locate' | translate}}</ion-label>
|
||||
<ng-template #loading>
|
||||
<ion-skeleton-text animated='true'></ion-skeleton-text>
|
||||
</ng-template>
|
||||
</ion-chip>
|
||||
@@ -25,6 +25,10 @@ import {MomentModule} from 'ngx-moment';
|
||||
import {ThingTranslateModule} from '../../translation/thing-translate.module';
|
||||
import {MenuModule} from '../menu/menu.module';
|
||||
import {StorageModule} from '../storage/storage.module';
|
||||
import {ActionChipListComponent} from './chips/action-chip-list.component';
|
||||
import {AddEventPopoverComponent} from './chips/add-event-popover.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';
|
||||
import {DataIconPipe} from './data-icon.pipe';
|
||||
import {DataRoutingModule} from './data-routing.module';
|
||||
@@ -77,6 +81,7 @@ import {VideoListItem} from './types/video/video-list-item.component';
|
||||
*/
|
||||
@NgModule({
|
||||
declarations: [
|
||||
AddEventPopoverComponent,
|
||||
OffersDetailComponent,
|
||||
OffersInListComponent,
|
||||
AddressDetailComponent,
|
||||
@@ -119,6 +124,9 @@ import {VideoListItem} from './types/video/video-list-item.component';
|
||||
VideoDetailContentComponent,
|
||||
VideoListItem,
|
||||
DataIconPipe,
|
||||
ActionChipListComponent,
|
||||
AddEventActionChipComponent,
|
||||
LocateActionChipComponent,
|
||||
],
|
||||
entryComponents: [
|
||||
DataListComponent,
|
||||
|
||||
@@ -7,7 +7,8 @@
|
||||
::ng-deep {
|
||||
ion-slides.work-locations {
|
||||
ion-slide {
|
||||
display: block; text-align: left;
|
||||
display: block;
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,19 @@
|
||||
<ion-item>
|
||||
<ion-thumbnail slot="start" class="ion-margin-end">
|
||||
<ion-thumbnail slot='start' class='ion-margin-end'>
|
||||
<ion-skeleton-text animated></ion-skeleton-text>
|
||||
</ion-thumbnail>
|
||||
<ion-grid>
|
||||
<ion-row>
|
||||
<ion-col>
|
||||
<h2 class="name"><ion-skeleton-text animated style="width: 80%"></ion-skeleton-text></h2>
|
||||
<p><ion-skeleton-text animated style="width: 80%;"></ion-skeleton-text></p>
|
||||
<ion-note><ion-skeleton-text animated style="width: 20%"></ion-skeleton-text></ion-note>
|
||||
<h2 class='name'>
|
||||
<ion-skeleton-text animated style='width: 80%'></ion-skeleton-text>
|
||||
</h2>
|
||||
<p>
|
||||
<ion-skeleton-text animated style='width: 80%;'></ion-skeleton-text>
|
||||
</p>
|
||||
<ion-note>
|
||||
<ion-skeleton-text animated style='width: 20%'></ion-skeleton-text>
|
||||
</ion-note>
|
||||
</ion-col>
|
||||
</ion-row>
|
||||
</ion-grid>
|
||||
|
||||
@@ -1,30 +1,36 @@
|
||||
<ion-item button="true" lines="inset" (click)="notifySelect()">
|
||||
<ion-thumbnail slot="start" *ngIf="!hideThumbnail" class="ion-margin-end">
|
||||
<ion-icon color="medium" [attr.name]="item.type | dataIcon"></ion-icon>
|
||||
<ion-item class='ion-text-wrap' button='true' lines='inset' (click)='notifySelect()'>
|
||||
<div class='item-height-placeholder'></div>
|
||||
<ion-thumbnail slot='start' *ngIf='!hideThumbnail' class='ion-margin-end'>
|
||||
<ion-icon color='medium' [attr.name]='item.type | dataIcon'></ion-icon>
|
||||
</ion-thumbnail>
|
||||
<ion-label class="ion-text-wrap" [ngSwitch]="true">
|
||||
<stapps-catalog-list-item [item]="item" *ngSwitchCase="item.type === 'catalog'"></stapps-catalog-list-item>
|
||||
<stapps-date-series-list-item [item]="item" *ngSwitchCase="item.type === 'date series'"></stapps-date-series-list-item>
|
||||
<stapps-dish-list-item [item]="item" *ngSwitchCase="item.type === 'dish'"></stapps-dish-list-item>
|
||||
<stapps-event-list-item [item]="item" *ngSwitchCase="item.type === 'academic event'"></stapps-event-list-item>
|
||||
<stapps-event-list-item [item]="item" *ngSwitchCase="item.type === 'sport course'"></stapps-event-list-item>
|
||||
<stapps-favorite-list-item [item]="item" *ngSwitchCase="item.type === 'favorite'"></stapps-favorite-list-item>
|
||||
<stapps-message-list-item [item]="item" *ngSwitchCase="item.type === 'message'"></stapps-message-list-item>
|
||||
<stapps-organization-list-item [item]="item" *ngSwitchCase="item.type === 'organization'"></stapps-organization-list-item>
|
||||
<stapps-person-list-item [item]="item" *ngSwitchCase="item.type === 'person'"></stapps-person-list-item>
|
||||
<stapps-place-list-item [item]="item" *ngSwitchCase="item.type === 'building'"></stapps-place-list-item>
|
||||
<stapps-place-list-item [item]="item" *ngSwitchCase="item.type === 'floor'"></stapps-place-list-item>
|
||||
<stapps-place-list-item [item]="item" *ngSwitchCase="item.type === 'point of interest'"></stapps-place-list-item>
|
||||
<stapps-place-list-item [item]="item" *ngSwitchCase="item.type === 'room'"></stapps-place-list-item>
|
||||
<stapps-semester-list-item [item]="item" *ngSwitchCase="item.type === 'semester'"></stapps-semester-list-item>
|
||||
<stapps-video-list-item [item]="item" *ngSwitchCase="item.type === 'video'"></stapps-video-list-item>
|
||||
<div *ngSwitchDefault>
|
||||
<h2>
|
||||
{{'name' | thingTranslate: item}}
|
||||
</h2>
|
||||
<p *ngIf="item.description">
|
||||
<stapps-long-inline-text [text]="'description' | thingTranslate: item" [size]="80"></stapps-long-inline-text>
|
||||
</p>
|
||||
<ion-label class='ion-text-wrap' [ngSwitch]='true'>
|
||||
<div>
|
||||
<stapps-catalog-list-item [item]='item' *ngSwitchCase="item.type === 'catalog'"></stapps-catalog-list-item>
|
||||
<stapps-date-series-list-item [item]='item'
|
||||
*ngSwitchCase="item.type === 'date series'"></stapps-date-series-list-item>
|
||||
<stapps-dish-list-item [item]='item' *ngSwitchCase="item.type === 'dish'"></stapps-dish-list-item>
|
||||
<stapps-event-list-item [item]='item' *ngSwitchCase="item.type === 'academic event'"></stapps-event-list-item>
|
||||
<stapps-event-list-item [item]='item' *ngSwitchCase="item.type === 'sport course'"></stapps-event-list-item>
|
||||
<stapps-favorite-list-item [item]='item' *ngSwitchCase="item.type === 'favorite'"></stapps-favorite-list-item>
|
||||
<stapps-message-list-item [item]='item' *ngSwitchCase="item.type === 'message'"></stapps-message-list-item>
|
||||
<stapps-organization-list-item [item]='item'
|
||||
*ngSwitchCase="item.type === 'organization'"></stapps-organization-list-item>
|
||||
<stapps-person-list-item [item]='item' *ngSwitchCase="item.type === 'person'"></stapps-person-list-item>
|
||||
<stapps-place-list-item [item]='item' *ngSwitchCase="item.type === 'building'"></stapps-place-list-item>
|
||||
<stapps-place-list-item [item]='item' *ngSwitchCase="item.type === 'floor'"></stapps-place-list-item>
|
||||
<stapps-place-list-item [item]='item' *ngSwitchCase="item.type === 'point of interest'"></stapps-place-list-item>
|
||||
<stapps-place-list-item [item]='item' *ngSwitchCase="item.type === 'room'"></stapps-place-list-item>
|
||||
<stapps-semester-list-item [item]='item' *ngSwitchCase="item.type === 'semester'"></stapps-semester-list-item>
|
||||
<stapps-video-list-item [item]='item' *ngSwitchCase="item.type === 'video'"></stapps-video-list-item>
|
||||
<div *ngSwitchDefault>
|
||||
<h2>
|
||||
{{'name' | thingTranslate: item}}
|
||||
</h2>
|
||||
<p *ngIf='item.description'>
|
||||
<stapps-long-inline-text [text]="'description' | thingTranslate: item" [size]='80'></stapps-long-inline-text>
|
||||
</p>
|
||||
</div>
|
||||
<stapps-action-chip-list slot='end' [item]='item'></stapps-action-chip-list>
|
||||
</div>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
|
||||
@@ -1,3 +1,13 @@
|
||||
.item {
|
||||
ion-label {
|
||||
width: 100%;
|
||||
|
||||
div {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
::ng-deep {
|
||||
ion-grid, ion-col {
|
||||
|
||||
@@ -13,8 +13,19 @@
|
||||
* this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import {CdkVirtualScrollViewport} from '@angular/cdk/scrolling';
|
||||
import {Component, EventEmitter, Input, OnChanges, OnInit, Output, SimpleChanges, ViewChild} from '@angular/core';
|
||||
import {
|
||||
Component,
|
||||
EventEmitter,
|
||||
HostListener,
|
||||
Input,
|
||||
OnChanges,
|
||||
OnInit,
|
||||
Output,
|
||||
SimpleChanges,
|
||||
ViewChild,
|
||||
} from '@angular/core';
|
||||
import {SCThings} from '@openstapps/core';
|
||||
import {ceil} from 'lodash-es';
|
||||
import {BehaviorSubject, Observable, Subscription} from 'rxjs';
|
||||
|
||||
/**
|
||||
@@ -51,6 +62,10 @@ export class DataListComponent implements OnChanges, OnInit {
|
||||
* Indicates whether or not the list is to display SCThings of a single type
|
||||
*/
|
||||
@Input() singleType = false;
|
||||
/**
|
||||
* Items that display the skeleton list
|
||||
*/
|
||||
skeletonItems: number[];
|
||||
/**
|
||||
* Array of all subscriptions to Observables
|
||||
*/
|
||||
@@ -58,9 +73,19 @@ export class DataListComponent implements OnChanges, OnInit {
|
||||
// tslint:disable-next-line: completed-docs
|
||||
@ViewChild(CdkVirtualScrollViewport) viewPort: CdkVirtualScrollViewport;
|
||||
|
||||
/**
|
||||
* Calculate how many items would fill the screen
|
||||
*/
|
||||
@HostListener('window.resize', ['$event'])
|
||||
calcSkeletonItems() {
|
||||
const itemHeight = 122;
|
||||
this.skeletonItems = new Array(ceil(window.innerHeight / itemHeight));
|
||||
}
|
||||
|
||||
/**
|
||||
* Uniquely identifies item at a certain list index
|
||||
*/
|
||||
// tslint:disable-next-line:prefer-function-over-method
|
||||
identifyItem(_index: number, item: SCThings) {
|
||||
return item.uid;
|
||||
}
|
||||
@@ -73,18 +98,19 @@ export class DataListComponent implements OnChanges, OnInit {
|
||||
}
|
||||
|
||||
// tslint:disable-next-line: completed-docs
|
||||
ngOnInit(): void {
|
||||
if (typeof this.resetToTop !== 'undefined') {
|
||||
this.subscriptions.push(this.resetToTop.subscribe(() => {
|
||||
this.viewPort.scrollToIndex(0);
|
||||
}));
|
||||
ngOnDestroy(): void {
|
||||
for (const subscription of this.subscriptions) {
|
||||
subscription.unsubscribe();
|
||||
}
|
||||
}
|
||||
|
||||
// tslint:disable-next-line: completed-docs
|
||||
ngOnDestroy(): void {
|
||||
for (const subscription of this.subscriptions) {
|
||||
subscription.unsubscribe();
|
||||
ngOnInit(): void {
|
||||
this.calcSkeletonItems();
|
||||
if (typeof this.resetToTop !== 'undefined') {
|
||||
this.subscriptions.push(this.resetToTop.subscribe(() => {
|
||||
this.viewPort.scrollToIndex(0);
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -13,5 +13,5 @@
|
||||
</ion-label>
|
||||
</div>
|
||||
<ion-list [style.display]="items ? 'none': 'block'">
|
||||
<stapps-skeleton-list-item *ngFor="let skeleton of [1, 2, 3, 4, 5]"></stapps-skeleton-list-item>
|
||||
<stapps-skeleton-list-item *ngFor="let skeleton of skeletonItems"></stapps-skeleton-list-item>
|
||||
</ion-list>
|
||||
|
||||
Reference in New Issue
Block a user