mirror of
https://gitlab.com/openstapps/openstapps.git
synced 2026-01-21 17:12:43 +00:00
fix: parallax broken since safari 16.4
Moves the parallax effect into a directive which injects required elements into the shadow DOM and adds `part` attributes, so it can be styled from a global stylesheet.
This commit is contained in:
@@ -22,10 +22,8 @@
|
|||||||
<!-- TODO: translation -->
|
<!-- TODO: translation -->
|
||||||
</ion-toolbar>
|
</ion-toolbar>
|
||||||
</ion-header>
|
</ion-header>
|
||||||
<ion-content class="ion-content-parallax">
|
<ion-content parallax>
|
||||||
<div>
|
<div class="about-changelog">
|
||||||
<div class="about-changelog">
|
<markdown src="assets/about/CHANGELOG.md"></markdown>
|
||||||
<markdown src="assets/about/CHANGELOG.md"></markdown>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</ion-content>
|
</ion-content>
|
||||||
|
|||||||
@@ -22,32 +22,30 @@
|
|||||||
<!-- TODO: translation -->
|
<!-- TODO: translation -->
|
||||||
</ion-toolbar>
|
</ion-toolbar>
|
||||||
</ion-header>
|
</ion-header>
|
||||||
<ion-content class="ion-content-parallax">
|
<ion-content parallax>
|
||||||
<div>
|
<div class="licenses-content">
|
||||||
<div class="licenses-content">
|
<ion-card
|
||||||
<ion-card
|
*ngFor="let license of licenses"
|
||||||
*ngFor="let license of licenses"
|
[href]="license.url || license.repository"
|
||||||
[href]="license.url || license.repository"
|
rel="external"
|
||||||
rel="external"
|
target="_blank"
|
||||||
target="_blank"
|
>
|
||||||
>
|
<ion-card-header>
|
||||||
<ion-card-header>
|
<ion-card-title>
|
||||||
<ion-card-title>
|
{{ license.name }}
|
||||||
{{ license.name }}
|
<ion-icon size="16" weight="300" class="supertext-icon" name="open_in_browser"></ion-icon>
|
||||||
<ion-icon size="16" weight="300" class="supertext-icon" name="open_in_browser"></ion-icon>
|
</ion-card-title>
|
||||||
</ion-card-title>
|
|
||||||
|
|
||||||
<ion-card-subtitle *ngIf="license.authors || license.publisher">
|
<ion-card-subtitle *ngIf="license.authors || license.publisher">
|
||||||
{{ license.authors || license.publisher }}
|
{{ license.authors || license.publisher }}
|
||||||
</ion-card-subtitle>
|
</ion-card-subtitle>
|
||||||
</ion-card-header>
|
</ion-card-header>
|
||||||
<ion-card-content>
|
<ion-card-content>
|
||||||
<ion-chip (click)="$event.preventDefault(); viewLicense(license)">
|
<ion-chip (click)="$event.preventDefault(); viewLicense(license)">
|
||||||
<ion-icon name="copyright"></ion-icon>
|
<ion-icon name="copyright"></ion-icon>
|
||||||
<ion-label>{{ license.licenses }} License</ion-label>
|
<ion-label>{{ license.licenses }} License</ion-label>
|
||||||
</ion-chip>
|
</ion-chip>
|
||||||
</ion-card-content>
|
</ion-card-content>
|
||||||
</ion-card>
|
</ion-card>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</ion-content>
|
</ion-content>
|
||||||
|
|||||||
@@ -24,11 +24,9 @@
|
|||||||
</ng-template>
|
</ng-template>
|
||||||
</ion-toolbar>
|
</ion-toolbar>
|
||||||
</ion-header>
|
</ion-header>
|
||||||
<ion-content *ngIf="content" class="ion-content-parallax">
|
<ion-content parallax *ngIf="content">
|
||||||
<div>
|
<ion-text>{{ appName }} v{{ version }}</ion-text>
|
||||||
<ion-text>{{ appName }} v{{ version }}</ion-text>
|
<div class="page-content">
|
||||||
<div class="page-content">
|
<about-page-content *ngFor="let element of content.content" [content]="element"></about-page-content>
|
||||||
<about-page-content *ngFor="let element of content.content" [content]="element"></about-page-content>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</ion-content>
|
</ion-content>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<!--
|
<!--
|
||||||
~ Copyright (C) 2022 StApps
|
~ Copyright (C) 2023 StApps
|
||||||
~ This program is free software: you can redistribute it and/or modify it
|
~ 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
|
~ under the terms of the GNU General Public License as published by the Free
|
||||||
~ Software Foundation, version 3.
|
~ Software Foundation, version 3.
|
||||||
@@ -26,14 +26,12 @@
|
|||||||
<ion-label>{{ label | translate }}</ion-label>
|
<ion-label>{{ label | translate }}</ion-label>
|
||||||
<stapps-edit-modal #editModal (save)="selection.save()">
|
<stapps-edit-modal #editModal (save)="selection.save()">
|
||||||
<ng-template>
|
<ng-template>
|
||||||
<ion-content class="ion-padding modal-content">
|
<ion-content parallax [parallaxSize]="160" class="ion-padding modal-content">
|
||||||
<div>
|
<stapps-edit-event-selection
|
||||||
<stapps-edit-event-selection
|
#selection
|
||||||
#selection
|
[items]="associatedDateSeries"
|
||||||
[items]="associatedDateSeries"
|
(modified)="editModal.pendingChanges = true"
|
||||||
(modified)="editModal.pendingChanges = true"
|
></stapps-edit-event-selection>
|
||||||
></stapps-edit-event-selection>
|
|
||||||
</div>
|
|
||||||
</ion-content>
|
</ion-content>
|
||||||
<ion-footer mode="ios">
|
<ion-footer mode="ios">
|
||||||
<ion-toolbar color="light">
|
<ion-toolbar color="light">
|
||||||
|
|||||||
@@ -12,8 +12,6 @@
|
|||||||
* You should have received a copy of the GNU General Public License along with
|
* You should have received a copy of the GNU General Public License along with
|
||||||
* this program. If not, see <https://www.gnu.org/licenses/>.
|
* this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
@import 'src/theme/common/ion-content-parallax';
|
|
||||||
|
|
||||||
:host {
|
:host {
|
||||||
display: block;
|
display: block;
|
||||||
padding: var(--spacing-sm);
|
padding: var(--spacing-sm);
|
||||||
@@ -35,10 +33,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.modal-content {
|
.modal-content {
|
||||||
--background: var(--ion-color-primary);
|
|
||||||
--color: var(--ion-color-primary-contrast);
|
--color: var(--ion-color-primary-contrast);
|
||||||
|
|
||||||
@include ion-content-parallax($content-size: 160px);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ion-footer > ion-toolbar {
|
ion-footer > ion-toolbar {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<!--
|
<!--
|
||||||
~ Copyright (C) 2022 StApps
|
~ Copyright (C) 2023 StApps
|
||||||
~ This program is free software: you can redistribute it and/or modify it
|
~ 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
|
~ under the terms of the GNU General Public License as published by the Free
|
||||||
~ Software Foundation, version 3.
|
~ Software Foundation, version 3.
|
||||||
@@ -30,8 +30,8 @@
|
|||||||
</ion-toolbar>
|
</ion-toolbar>
|
||||||
</ion-header>
|
</ion-header>
|
||||||
<ng-content select="[header]"></ng-content>
|
<ng-content select="[header]"></ng-content>
|
||||||
<ion-content class="ion-no-padding ion-content-parallax">
|
<ion-content parallax class="ion-no-padding">
|
||||||
<div [ngSwitch]="true">
|
<ng-container [ngSwitch]="true">
|
||||||
<ng-container *ngSwitchCase="!item && (isDisconnected | async)">
|
<ng-container *ngSwitchCase="!item && (isDisconnected | async)">
|
||||||
<div class="centeredMessageContainer">
|
<div class="centeredMessageContainer">
|
||||||
<ion-icon name="signal_disconnected"></ion-icon>
|
<ion-icon name="signal_disconnected"></ion-icon>
|
||||||
@@ -59,5 +59,5 @@
|
|||||||
[contentTemplateRef]="contentTemplateRef"
|
[contentTemplateRef]="contentTemplateRef"
|
||||||
></stapps-data-detail-content>
|
></stapps-data-detail-content>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
</div>
|
</ng-container>
|
||||||
</ion-content>
|
</ion-content>
|
||||||
|
|||||||
@@ -12,11 +12,6 @@
|
|||||||
* You should have received a copy of the GNU General Public License along with
|
* You should have received a copy of the GNU General Public License along with
|
||||||
* this program. If not, see <https://www.gnu.org/licenses/>.
|
* this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
:host {
|
|
||||||
z-index: 100;
|
|
||||||
}
|
|
||||||
|
|
||||||
.crumb-label {
|
.crumb-label {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
|
|||||||
@@ -21,107 +21,105 @@
|
|||||||
<ion-title>{{ 'feedback.page.TITLE' | translate }}</ion-title>
|
<ion-title>{{ 'feedback.page.TITLE' | translate }}</ion-title>
|
||||||
</ion-toolbar>
|
</ion-toolbar>
|
||||||
</ion-header>
|
</ion-header>
|
||||||
<ion-content class="ion-content-parallax">
|
<ion-content parallax>
|
||||||
<div>
|
<div class="feedback-content">
|
||||||
<div class="feedback-content">
|
<ion-card>
|
||||||
<ion-card>
|
<form #feedbackForm="ngForm" (ngSubmit)="onSubmit()">
|
||||||
<form #feedbackForm="ngForm" (ngSubmit)="onSubmit()">
|
<ion-item>
|
||||||
<ion-item>
|
<ion-label position="stacked">{{ 'feedback.form.name.label' | translate }}</ion-label>
|
||||||
<ion-label position="stacked">{{ 'feedback.form.name.label' | translate }}</ion-label>
|
<ion-input
|
||||||
<ion-input
|
placeholder="{{ 'feedback.form.name.placeholder' | translate }}"
|
||||||
placeholder="{{ 'feedback.form.name.placeholder' | translate }}"
|
[(ngModel)]="author.name"
|
||||||
[(ngModel)]="author.name"
|
name="name"
|
||||||
name="name"
|
></ion-input>
|
||||||
></ion-input>
|
</ion-item>
|
||||||
</ion-item>
|
<ion-item>
|
||||||
<ion-item>
|
<ion-label position="stacked">{{ 'feedback.form.type.label' | translate }}</ion-label>
|
||||||
<ion-label position="stacked">{{ 'feedback.form.type.label' | translate }}</ion-label>
|
<ion-select
|
||||||
<ion-select
|
[(ngModel)]="message.name"
|
||||||
[(ngModel)]="message.name"
|
value="comment"
|
||||||
value="comment"
|
name="title"
|
||||||
name="title"
|
interface="popover"
|
||||||
interface="popover"
|
required="true"
|
||||||
required="true"
|
|
||||||
>
|
|
||||||
<ion-select-option value="Comment">{{
|
|
||||||
'feedback.form.type.values.comment' | translate
|
|
||||||
}}</ion-select-option>
|
|
||||||
<ion-select-option value="Bug">{{
|
|
||||||
'feedback.form.type.values.bug' | translate
|
|
||||||
}}</ion-select-option>
|
|
||||||
</ion-select>
|
|
||||||
</ion-item>
|
|
||||||
<ion-item>
|
|
||||||
<ion-label position="stacked">{{ 'feedback.form.email.label' | translate }}</ion-label>
|
|
||||||
<ion-input
|
|
||||||
placeholder="{{ 'feedback.form.email.placeholder' | translate }}"
|
|
||||||
[(ngModel)]="author.email"
|
|
||||||
type="email"
|
|
||||||
name="email"
|
|
||||||
ngModel
|
|
||||||
email
|
|
||||||
></ion-input>
|
|
||||||
</ion-item>
|
|
||||||
<ion-item>
|
|
||||||
<ion-label position="stacked">{{ 'feedback.form.message.label' | translate }}</ion-label>
|
|
||||||
<ion-textarea
|
|
||||||
[(ngModel)]="message.messageBody"
|
|
||||||
placeholder="{{
|
|
||||||
'feedback.form.message.placeholder' | translate: {number: MINIMUM_MESSAGE_SIZE}
|
|
||||||
}}"
|
|
||||||
name="message"
|
|
||||||
required="true"
|
|
||||||
minlength="{{ MINIMUM_MESSAGE_SIZE }}"
|
|
||||||
autoGrow="true"
|
|
||||||
></ion-textarea>
|
|
||||||
</ion-item>
|
|
||||||
<ion-item>
|
|
||||||
<ion-label class="ion-text-wrap">{{ 'feedback.form.termsAgree.0' | translate }} </ion-label>
|
|
||||||
<ion-checkbox
|
|
||||||
color="primary"
|
|
||||||
slot="start"
|
|
||||||
[(ngModel)]="termsAgree"
|
|
||||||
name="termsAgree"
|
|
||||||
></ion-checkbox>
|
|
||||||
</ion-item>
|
|
||||||
<ion-item lines="none">
|
|
||||||
<ion-label
|
|
||||||
><a style="display: contents" [routerLink]="['/about/privacy']">{{
|
|
||||||
'feedback.form.termsAgree.1' | translate
|
|
||||||
}}</a></ion-label
|
|
||||||
>
|
|
||||||
</ion-item>
|
|
||||||
<ion-item>
|
|
||||||
<ion-label class="ion-text-wrap">{{ 'feedback.form.protocolDataAgree' | translate }}</ion-label>
|
|
||||||
<ion-checkbox
|
|
||||||
color="primary"
|
|
||||||
slot="start"
|
|
||||||
[(ngModel)]="protocolDataAgree"
|
|
||||||
name="protocolDataAgree"
|
|
||||||
></ion-checkbox>
|
|
||||||
</ion-item>
|
|
||||||
<ion-card>
|
|
||||||
<ion-card-title>
|
|
||||||
<ion-button expand="block" fill="clear" (click)="toggleShowMetaData()">
|
|
||||||
<ng-container *ngIf="!showMetaData; else hide">{{
|
|
||||||
'feedback.form.protocolData.show' | translate
|
|
||||||
}}</ng-container>
|
|
||||||
<ng-template #hide>{{ 'feedback.form.protocolData.hide' | translate }}</ng-template>
|
|
||||||
</ion-button>
|
|
||||||
</ion-card-title>
|
|
||||||
<ion-card-content *ngIf="metaData && showMetaData">
|
|
||||||
<pre>{{ metaData | json }}</pre>
|
|
||||||
</ion-card-content>
|
|
||||||
</ion-card>
|
|
||||||
<ion-button
|
|
||||||
type="submit"
|
|
||||||
color="primary"
|
|
||||||
expand="block"
|
|
||||||
[disabled]="!feedbackForm.valid || !termsAgree || submitSuccess"
|
|
||||||
>{{ 'feedback.form.submit' | translate }}</ion-button
|
|
||||||
>
|
>
|
||||||
</form>
|
<ion-select-option value="Comment">{{
|
||||||
</ion-card>
|
'feedback.form.type.values.comment' | translate
|
||||||
</div>
|
}}</ion-select-option>
|
||||||
|
<ion-select-option value="Bug">{{
|
||||||
|
'feedback.form.type.values.bug' | translate
|
||||||
|
}}</ion-select-option>
|
||||||
|
</ion-select>
|
||||||
|
</ion-item>
|
||||||
|
<ion-item>
|
||||||
|
<ion-label position="stacked">{{ 'feedback.form.email.label' | translate }}</ion-label>
|
||||||
|
<ion-input
|
||||||
|
placeholder="{{ 'feedback.form.email.placeholder' | translate }}"
|
||||||
|
[(ngModel)]="author.email"
|
||||||
|
type="email"
|
||||||
|
name="email"
|
||||||
|
ngModel
|
||||||
|
email
|
||||||
|
></ion-input>
|
||||||
|
</ion-item>
|
||||||
|
<ion-item>
|
||||||
|
<ion-label position="stacked">{{ 'feedback.form.message.label' | translate }}</ion-label>
|
||||||
|
<ion-textarea
|
||||||
|
[(ngModel)]="message.messageBody"
|
||||||
|
placeholder="{{
|
||||||
|
'feedback.form.message.placeholder' | translate: {number: MINIMUM_MESSAGE_SIZE}
|
||||||
|
}}"
|
||||||
|
name="message"
|
||||||
|
required="true"
|
||||||
|
minlength="{{ MINIMUM_MESSAGE_SIZE }}"
|
||||||
|
autoGrow="true"
|
||||||
|
></ion-textarea>
|
||||||
|
</ion-item>
|
||||||
|
<ion-item>
|
||||||
|
<ion-label class="ion-text-wrap">{{ 'feedback.form.termsAgree.0' | translate }} </ion-label>
|
||||||
|
<ion-checkbox
|
||||||
|
color="primary"
|
||||||
|
slot="start"
|
||||||
|
[(ngModel)]="termsAgree"
|
||||||
|
name="termsAgree"
|
||||||
|
></ion-checkbox>
|
||||||
|
</ion-item>
|
||||||
|
<ion-item lines="none">
|
||||||
|
<ion-label
|
||||||
|
><a style="display: contents" [routerLink]="['/about/privacy']">{{
|
||||||
|
'feedback.form.termsAgree.1' | translate
|
||||||
|
}}</a></ion-label
|
||||||
|
>
|
||||||
|
</ion-item>
|
||||||
|
<ion-item>
|
||||||
|
<ion-label class="ion-text-wrap">{{ 'feedback.form.protocolDataAgree' | translate }}</ion-label>
|
||||||
|
<ion-checkbox
|
||||||
|
color="primary"
|
||||||
|
slot="start"
|
||||||
|
[(ngModel)]="protocolDataAgree"
|
||||||
|
name="protocolDataAgree"
|
||||||
|
></ion-checkbox>
|
||||||
|
</ion-item>
|
||||||
|
<ion-card>
|
||||||
|
<ion-card-title>
|
||||||
|
<ion-button expand="block" fill="clear" (click)="toggleShowMetaData()">
|
||||||
|
<ng-container *ngIf="!showMetaData; else hide">{{
|
||||||
|
'feedback.form.protocolData.show' | translate
|
||||||
|
}}</ng-container>
|
||||||
|
<ng-template #hide>{{ 'feedback.form.protocolData.hide' | translate }}</ng-template>
|
||||||
|
</ion-button>
|
||||||
|
</ion-card-title>
|
||||||
|
<ion-card-content *ngIf="metaData && showMetaData">
|
||||||
|
<pre>{{ metaData | json }}</pre>
|
||||||
|
</ion-card-content>
|
||||||
|
</ion-card>
|
||||||
|
<ion-button
|
||||||
|
type="submit"
|
||||||
|
color="primary"
|
||||||
|
expand="block"
|
||||||
|
[disabled]="!feedbackForm.valid || !termsAgree || submitSuccess"
|
||||||
|
>{{ 'feedback.form.submit' | translate }}</ion-button
|
||||||
|
>
|
||||||
|
</form>
|
||||||
|
</ion-card>
|
||||||
</div>
|
</div>
|
||||||
</ion-content>
|
</ion-content>
|
||||||
|
|||||||
@@ -22,41 +22,39 @@
|
|||||||
</ion-toolbar>
|
</ion-toolbar>
|
||||||
</ion-header>
|
</ion-header>
|
||||||
|
|
||||||
<ion-content class="ion-content-parallax" (elementSizeChange)="calcPageSize($event)">
|
<ion-content parallax (elementSizeChange)="calcPageSize($event)">
|
||||||
<div>
|
<ion-refresher slot="fixed" (ionRefresh)="refresh($event.target)">
|
||||||
<ion-refresher slot="fixed" (ionRefresh)="refresh($event.target)">
|
<ion-refresher-content
|
||||||
<ion-refresher-content
|
pullingIcon="chevron-down-outline"
|
||||||
pullingIcon="chevron-down-outline"
|
pullingText="{{ 'data.REFRESH_ACTION' | translate }}"
|
||||||
pullingText="{{ 'data.REFRESH_ACTION' | translate }}"
|
refreshingText="{{ 'data.REFRESHING' | translate }}"
|
||||||
refreshingText="{{ 'data.REFRESHING' | translate }}"
|
refreshingSpinner="crescent"
|
||||||
refreshingSpinner="crescent"
|
>
|
||||||
>
|
</ion-refresher-content>
|
||||||
</ion-refresher-content>
|
</ion-refresher>
|
||||||
</ion-refresher>
|
<ion-grid>
|
||||||
<ion-grid>
|
<ion-row>
|
||||||
<ion-row>
|
<ion-col size="12">
|
||||||
<ion-col size="12">
|
<stapps-news-settings-filter
|
||||||
<stapps-news-settings-filter
|
*ngIf="settings"
|
||||||
*ngIf="settings"
|
[settings]="settings"
|
||||||
[settings]="settings"
|
(filtersChanged)="toggleFilter($event)"
|
||||||
(filtersChanged)="toggleFilter($event)"
|
></stapps-news-settings-filter>
|
||||||
></stapps-news-settings-filter>
|
</ion-col>
|
||||||
</ion-col>
|
</ion-row>
|
||||||
</ion-row>
|
</ion-grid>
|
||||||
</ion-grid>
|
<div class="news-grid">
|
||||||
<div class="news-grid">
|
<ng-container *ngIf="!news">
|
||||||
<ng-container *ngIf="!news">
|
<stapps-skeleton-news-item *ngFor="let skeleton of [1, 2, 3, 4, 5]"></stapps-skeleton-news-item>
|
||||||
<stapps-skeleton-news-item *ngFor="let skeleton of [1, 2, 3, 4, 5]"></stapps-skeleton-news-item>
|
</ng-container>
|
||||||
</ng-container>
|
<ng-container *ngIf="news.length > 0">
|
||||||
<ng-container *ngIf="news.length > 0">
|
<stapps-news-item *ngFor="let item of news" [item]="item"></stapps-news-item>
|
||||||
<stapps-news-item *ngFor="let item of news" [item]="item"></stapps-news-item>
|
</ng-container>
|
||||||
</ng-container>
|
|
||||||
</div>
|
|
||||||
<ion-label *ngIf="news.length === 0" class="centeredMessageContainer">
|
|
||||||
{{ 'search.nothing_found' | translate | titlecase }}
|
|
||||||
</ion-label>
|
|
||||||
<ion-infinite-scroll id="infinite-scroll" threshold="20%" (ionInfinite)="loadMore($event.target)">
|
|
||||||
<ion-infinite-scroll-content loading-spinner="crescent"> </ion-infinite-scroll-content>
|
|
||||||
</ion-infinite-scroll>
|
|
||||||
</div>
|
</div>
|
||||||
|
<ion-label *ngIf="news.length === 0" class="centeredMessageContainer">
|
||||||
|
{{ 'search.nothing_found' | translate | titlecase }}
|
||||||
|
</ion-label>
|
||||||
|
<ion-infinite-scroll id="infinite-scroll" threshold="20%" (ionInfinite)="loadMore($event.target)">
|
||||||
|
<ion-infinite-scroll-content loading-spinner="crescent"> </ion-infinite-scroll-content>
|
||||||
|
</ion-infinite-scroll>
|
||||||
</ion-content>
|
</ion-content>
|
||||||
|
|||||||
@@ -22,101 +22,97 @@
|
|||||||
</ion-toolbar>
|
</ion-toolbar>
|
||||||
</ion-header>
|
</ion-header>
|
||||||
|
|
||||||
<ion-content>
|
<ion-content color="light" parallax [parallaxSize]="130">
|
||||||
<div>
|
<section class="user-card-wrapper">
|
||||||
<section class="user-card-wrapper">
|
<ion-card class="user-card">
|
||||||
<ion-card class="user-card">
|
<ion-card-header>
|
||||||
<ion-card-header>
|
<ion-img src="assets/imgs/header.svg"></ion-img>
|
||||||
<ion-img src="assets/imgs/header.svg"></ion-img>
|
<span *ngIf="user$ | async as userInfo">
|
||||||
<span *ngIf="user$ | async as userInfo">
|
{{ userInfo.role ? (userInfo?.role | titlecase) : ('profile.role_guest' | translate | titlecase) }}
|
||||||
{{
|
</span>
|
||||||
userInfo.role ? (userInfo?.role | titlecase) : ('profile.role_guest' | translate | titlecase)
|
</ion-card-header>
|
||||||
}}
|
<ion-card-content>
|
||||||
</span>
|
<ion-img class="profile-card-img" src="assets/imgs/profile-card-head.svg"></ion-img>
|
||||||
</ion-card-header>
|
<ion-grid>
|
||||||
<ion-card-content>
|
<ion-row>
|
||||||
<ion-img class="profile-card-img" src="assets/imgs/profile-card-head.svg"></ion-img>
|
<ion-col size="3"></ion-col>
|
||||||
<ion-grid>
|
<ion-col
|
||||||
<ion-row>
|
*ngIf="data.default.loggedIn$ | async as loggedIn; else logInPrompt"
|
||||||
<ion-col size="3"></ion-col>
|
size="9"
|
||||||
<ion-col
|
class="main-info"
|
||||||
*ngIf="data.default.loggedIn$ | async as loggedIn; else logInPrompt"
|
>
|
||||||
size="9"
|
<ng-container *ngIf="user$ | async as userInfo">
|
||||||
class="main-info"
|
<ion-text class="full-name">
|
||||||
>
|
{{ userInfo?.name }}
|
||||||
<ng-container *ngIf="user$ | async as userInfo">
|
</ion-text>
|
||||||
<ion-text class="full-name">
|
<div class="matriculation-number">
|
||||||
{{ userInfo?.name }}
|
<ion-label>
|
||||||
|
{{ 'profile.userInfo.studentId' | translate | uppercase }}
|
||||||
|
</ion-label>
|
||||||
|
<ion-text>
|
||||||
|
{{ userInfo?.studentId }}
|
||||||
</ion-text>
|
</ion-text>
|
||||||
<div class="matriculation-number">
|
</div>
|
||||||
<ion-label>
|
<div class="user-name">
|
||||||
{{ 'profile.userInfo.studentId' | translate | uppercase }}
|
<ion-label>
|
||||||
</ion-label>
|
{{ 'profile.userInfo.username' | translate | uppercase }}
|
||||||
<ion-text>
|
</ion-label>
|
||||||
{{ userInfo?.studentId }}
|
<ion-text>{{ userInfo?.id }}</ion-text>
|
||||||
</ion-text>
|
</div>
|
||||||
</div>
|
<div class="email">
|
||||||
<div class="user-name">
|
<ion-label>
|
||||||
<ion-label>
|
{{ 'profile.userInfo.email' | translate | uppercase }}
|
||||||
{{ 'profile.userInfo.username' | translate | uppercase }}
|
</ion-label>
|
||||||
</ion-label>
|
<ion-text>
|
||||||
<ion-text>{{ userInfo?.id }}</ion-text>
|
{{ userInfo?.email }}
|
||||||
</div>
|
</ion-text>
|
||||||
<div class="email">
|
</div>
|
||||||
<ion-label>
|
</ng-container>
|
||||||
{{ 'profile.userInfo.email' | translate | uppercase }}
|
</ion-col>
|
||||||
</ion-label>
|
<ng-template #logInPrompt>
|
||||||
<ion-text>
|
<ion-col size="9">
|
||||||
{{ userInfo?.email }}
|
<ion-text class="log-in-prompt">
|
||||||
</ion-text>
|
{{ 'profile.userInfo.logInPrompt' | translate }}
|
||||||
</div>
|
</ion-text>
|
||||||
</ng-container>
|
|
||||||
</ion-col>
|
</ion-col>
|
||||||
<ng-template #logInPrompt>
|
</ng-template>
|
||||||
<ion-col size="9">
|
</ion-row>
|
||||||
<ion-text class="log-in-prompt">
|
</ion-grid>
|
||||||
{{ 'profile.userInfo.logInPrompt' | translate }}
|
</ion-card-content>
|
||||||
</ion-text>
|
</ion-card>
|
||||||
</ion-col>
|
</section>
|
||||||
</ng-template>
|
<stapps-profile-page-section
|
||||||
</ion-row>
|
*ngFor="let section of sections"
|
||||||
</ion-grid>
|
[item]="section"
|
||||||
</ion-card-content>
|
></stapps-profile-page-section>
|
||||||
</ion-card>
|
<section class="courses">
|
||||||
</section>
|
<ion-label class="section-headline">
|
||||||
<stapps-profile-page-section
|
{{ 'profile.titleCourses' | translate | uppercase }}
|
||||||
*ngFor="let section of sections"
|
</ion-label>
|
||||||
[item]="section"
|
<ion-card class="courses-card">
|
||||||
></stapps-profile-page-section>
|
<ion-card-header (click)="toggleCourseCardState()">
|
||||||
<section class="courses">
|
<span>{{ 'profile.courses.today' | translate | uppercase }}</span>
|
||||||
<ion-label class="section-headline">
|
<ion-icon [name]="courseCardState" color="dark" size="20"></ion-icon>
|
||||||
{{ 'profile.titleCourses' | translate | uppercase }}
|
</ion-card-header>
|
||||||
</ion-label>
|
<ion-card-content class="course-card" [class.show-card]="courseCardState === courseCardEnum.expanded">
|
||||||
<ion-card class="courses-card">
|
<ng-container *ngIf="myCoursesToday.length === 0">
|
||||||
<ion-card-header (click)="toggleCourseCardState()">
|
<div class="no-course">
|
||||||
<span>{{ 'profile.courses.today' | translate | uppercase }}</span>
|
{{ 'profile.courses.no_courses' | translate }}
|
||||||
<ion-icon [name]="courseCardState" color="dark" size="20"></ion-icon>
|
</div>
|
||||||
</ion-card-header>
|
</ng-container>
|
||||||
<ion-card-content class="course-card" [class.show-card]="courseCardState === courseCardEnum.expanded">
|
<ng-container *ngFor="let myCourse of myCoursesToday">
|
||||||
<ng-container *ngIf="myCoursesToday.length === 0">
|
<div class="clickable" [routerLink]="['/data-detail', myCourse.course.event.uid]">
|
||||||
<div class="no-course">
|
<div>{{ myCourse?.startTime }} - {{ myCourse?.endTime }}</div>
|
||||||
{{ 'profile.courses.no_courses' | translate }}
|
<div>{{ myCourse?.course.event?.originalCategory }}</div>
|
||||||
|
<div [class.last]="!myCourse?.course.inPlace?.name">
|
||||||
|
{{ myCourse.course?.event?.name }}
|
||||||
</div>
|
</div>
|
||||||
</ng-container>
|
<div *ngIf="myCourse.course?.inPlace?.name" [class.last]="myCourse.course?.inPlace?.name">
|
||||||
<ng-container *ngFor="let myCourse of myCoursesToday">
|
{{ myCourse.course?.inPlace.name }}
|
||||||
<div class="clickable" [routerLink]="['/data-detail', myCourse.course.event.uid]">
|
|
||||||
<div>{{ myCourse?.startTime }} - {{ myCourse?.endTime }}</div>
|
|
||||||
<div>{{ myCourse?.course.event?.originalCategory }}</div>
|
|
||||||
<div [class.last]="!myCourse?.course.inPlace?.name">
|
|
||||||
{{ myCourse.course?.event?.name }}
|
|
||||||
</div>
|
|
||||||
<div *ngIf="myCourse.course?.inPlace?.name" [class.last]="myCourse.course?.inPlace?.name">
|
|
||||||
{{ myCourse.course?.inPlace.name }}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</ng-container>
|
</div>
|
||||||
</ion-card-content>
|
</ng-container>
|
||||||
</ion-card>
|
</ion-card-content>
|
||||||
</section>
|
</ion-card>
|
||||||
</div>
|
</section>
|
||||||
</ion-content>
|
</ion-content>
|
||||||
|
|||||||
@@ -12,15 +12,7 @@
|
|||||||
* You should have received a copy of the GNU General Public License along with
|
* You should have received a copy of the GNU General Public License along with
|
||||||
* this program. If not, see <https://www.gnu.org/licenses/>.
|
* this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
@import 'src/theme/common/ion-content-parallax';
|
|
||||||
|
|
||||||
:host {
|
:host {
|
||||||
ion-content {
|
|
||||||
--background: var(--ion-color-light);
|
|
||||||
|
|
||||||
@include ion-content-parallax($content-size: 130px);
|
|
||||||
}
|
|
||||||
|
|
||||||
section {
|
section {
|
||||||
margin-bottom: calc(2 * var(--spacing-lg) - var(--spacing-md));
|
margin-bottom: calc(2 * var(--spacing-lg) - var(--spacing-md));
|
||||||
padding: var(--spacing-md);
|
padding: var(--spacing-md);
|
||||||
|
|||||||
@@ -22,12 +22,11 @@
|
|||||||
</ion-toolbar>
|
</ion-toolbar>
|
||||||
</ion-header>
|
</ion-header>
|
||||||
|
|
||||||
<ion-content class="ion-no-padding ion-content-parallax">
|
<ion-content parallax class="ion-no-padding">
|
||||||
<div>
|
<div class="settings-content">
|
||||||
<div class="settings-content">
|
<ng-container *ngFor="let categoryKey of categoriesOrder">
|
||||||
<ng-container *ngFor="let categoryKey of categoriesOrder">
|
<ion-list *ngIf="objectKeys(settingsCache).includes(categoryKey)">
|
||||||
<ion-list *ngIf="objectKeys(settingsCache).includes(categoryKey)">
|
<!-- <ion-item-divider>
|
||||||
<!-- <ion-item-divider>
|
|
||||||
<h2>
|
<h2>
|
||||||
{{
|
{{
|
||||||
'categories[0]'
|
'categories[0]'
|
||||||
@@ -41,19 +40,18 @@
|
|||||||
}}
|
}}
|
||||||
</h2>
|
</h2>
|
||||||
</ion-item-divider> -->
|
</ion-item-divider> -->
|
||||||
<stapps-settings-item
|
<stapps-settings-item
|
||||||
*ngFor="let settingKeys of objectKeys(settingsCache[categoryKey].settings)"
|
*ngFor="let settingKeys of objectKeys(settingsCache[categoryKey].settings)"
|
||||||
[setting]="settingsCache[categoryKey].settings[settingKeys]"
|
[setting]="settingsCache[categoryKey].settings[settingKeys]"
|
||||||
></stapps-settings-item>
|
></stapps-settings-item>
|
||||||
</ion-list>
|
</ion-list>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
<calendar-sync-settings></calendar-sync-settings>
|
<calendar-sync-settings></calendar-sync-settings>
|
||||||
|
|
||||||
<ion-button expand="block" (click)="presentResetAlert()" fill="outline">
|
<ion-button expand="block" (click)="presentResetAlert()" fill="outline">
|
||||||
{{ 'settings.resetSettings' | translate }}
|
{{ 'settings.resetSettings' | translate }}
|
||||||
<ion-icon slot="start" name="device_reset"></ion-icon>
|
<ion-icon slot="start" name="device_reset"></ion-icon>
|
||||||
</ion-button>
|
</ion-button>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</ion-content>
|
</ion-content>
|
||||||
|
|||||||
116
src/app/util/ion-content-parallax.directive.ts
Normal file
116
src/app/util/ion-content-parallax.directive.ts
Normal file
@@ -0,0 +1,116 @@
|
|||||||
|
/*
|
||||||
|
* 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 {Directive, ElementRef, HostBinding, Input, OnDestroy, OnInit} from '@angular/core';
|
||||||
|
|
||||||
|
type IonicColor =
|
||||||
|
| 'danger'
|
||||||
|
| 'dark'
|
||||||
|
| 'light'
|
||||||
|
| 'medium'
|
||||||
|
| 'primary'
|
||||||
|
| 'secondary'
|
||||||
|
| 'success'
|
||||||
|
| 'tertiary'
|
||||||
|
| 'warning'
|
||||||
|
| string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a parallax effect to `<ion-content/>` elements
|
||||||
|
*
|
||||||
|
* Why is this necessary and why do we need to inject
|
||||||
|
* elements into a foreign element's shadow DOM just for this?
|
||||||
|
*
|
||||||
|
* We previously had used a global style in conjunction with
|
||||||
|
* adding a child `<div/>`, however, unfortunately Safari 16.4
|
||||||
|
* broke 3D transforms where the parent is inside the shadow
|
||||||
|
* DOM and child elements are outside.
|
||||||
|
*
|
||||||
|
* We also have to use `<ion-content/>` as ionic relies on the
|
||||||
|
* `<ion-content/>` selector to make iOS animations work.
|
||||||
|
*
|
||||||
|
* Which means this is pretty much the only way to solve this
|
||||||
|
* problem.
|
||||||
|
*
|
||||||
|
* This directive, when applied using `<ion-content parallax/>`,
|
||||||
|
* will transform its shadow DOM from
|
||||||
|
*
|
||||||
|
* ```html
|
||||||
|
* <div id="background-content" part="background"></div>
|
||||||
|
* <main class="inner-scroll scroll-y" part="scroll">
|
||||||
|
* <slot></slot>
|
||||||
|
* </main>
|
||||||
|
* <slot name="fixed"><slot />
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* To
|
||||||
|
*
|
||||||
|
* ```html
|
||||||
|
* <div id="background-content" part="background"></div>
|
||||||
|
* <main class="inner-scroll scroll-y" part="scroll parallax-scroll">
|
||||||
|
* <div part="parallax-parent">
|
||||||
|
* <div part="parallax"></div>
|
||||||
|
* <slot></slot>
|
||||||
|
* </div>
|
||||||
|
* </main>
|
||||||
|
* <slot name="fixed"><slot />
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* Which we can then be used through `::part()` to style it from
|
||||||
|
* a global style sheet.
|
||||||
|
*/
|
||||||
|
@Directive({
|
||||||
|
selector: 'ion-content[parallax]',
|
||||||
|
})
|
||||||
|
export class IonContentParallaxDirective implements OnInit, OnDestroy {
|
||||||
|
@HostBinding('style.--parallax-content-size.px') @Input() parallaxSize = 230;
|
||||||
|
|
||||||
|
@Input() set parallaxColor(value: IonicColor) {
|
||||||
|
this.parallaxBackground = `var(--ion-color-${value})`;
|
||||||
|
}
|
||||||
|
|
||||||
|
@HostBinding('style.--parallax-background') parallaxBackground?: string;
|
||||||
|
|
||||||
|
private mutationObserver: MutationObserver;
|
||||||
|
|
||||||
|
constructor(private element: ElementRef) {}
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
this.mutationObserver = new MutationObserver(() => {
|
||||||
|
const inner = this.element.nativeElement.shadowRoot.querySelector('.inner-scroll') as
|
||||||
|
| HTMLDivElement
|
||||||
|
| undefined;
|
||||||
|
if (!inner) return;
|
||||||
|
|
||||||
|
// eslint-disable-next-line unicorn/no-array-for-each
|
||||||
|
inner.childNodes.forEach(node => node.remove());
|
||||||
|
|
||||||
|
inner.part.add('parallax-scroll');
|
||||||
|
const parallaxParentElement = document.createElement('div');
|
||||||
|
parallaxParentElement.part.add('parallax-parent');
|
||||||
|
const parallaxElement = document.createElement('div');
|
||||||
|
parallaxElement.part.add('parallax');
|
||||||
|
|
||||||
|
inner.append(parallaxParentElement);
|
||||||
|
parallaxParentElement.append(parallaxElement, document.createElement('slot'));
|
||||||
|
});
|
||||||
|
this.mutationObserver.observe(this.element.nativeElement.shadowRoot, {
|
||||||
|
childList: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnDestroy() {
|
||||||
|
this.mutationObserver.disconnect();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -31,10 +31,12 @@ import {SimpleSwiperComponent} from './simple-swiper.component';
|
|||||||
import {SearchbarAutofocusDirective} from './searchbar-autofocus.directive';
|
import {SearchbarAutofocusDirective} from './searchbar-autofocus.directive';
|
||||||
import {SectionComponent} from './section.component';
|
import {SectionComponent} from './section.component';
|
||||||
import {RouterModule} from '@angular/router';
|
import {RouterModule} from '@angular/router';
|
||||||
|
import {IonContentParallaxDirective} from './ion-content-parallax.directive';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [BrowserModule, IonicModule, TranslateModule, ThingTranslateModule.forChild(), RouterModule],
|
imports: [BrowserModule, IonicModule, TranslateModule, ThingTranslateModule.forChild(), RouterModule],
|
||||||
declarations: [
|
declarations: [
|
||||||
|
IonContentParallaxDirective,
|
||||||
ElementSizeChangeDirective,
|
ElementSizeChangeDirective,
|
||||||
ArrayLastPipe,
|
ArrayLastPipe,
|
||||||
DateIsThisPipe,
|
DateIsThisPipe,
|
||||||
@@ -50,6 +52,7 @@ import {RouterModule} from '@angular/router';
|
|||||||
SearchbarAutofocusDirective,
|
SearchbarAutofocusDirective,
|
||||||
],
|
],
|
||||||
exports: [
|
exports: [
|
||||||
|
IonContentParallaxDirective,
|
||||||
ElementSizeChangeDirective,
|
ElementSizeChangeDirective,
|
||||||
ArrayLastPipe,
|
ArrayLastPipe,
|
||||||
DateIsThisPipe,
|
DateIsThisPipe,
|
||||||
|
|||||||
@@ -124,10 +124,6 @@ ion-header {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.ion-content-parallax {
|
|
||||||
@include ion-content-parallax();
|
|
||||||
}
|
|
||||||
|
|
||||||
ion-alert {
|
ion-alert {
|
||||||
button.alert-button.preferred {
|
button.alert-button.preferred {
|
||||||
background-color: var(--ion-color-primary);
|
background-color: var(--ion-color-primary);
|
||||||
|
|||||||
@@ -12,43 +12,34 @@
|
|||||||
* You should have received a copy of the GNU General Public License along with
|
* You should have received a copy of the GNU General Public License along with
|
||||||
* this program. If not, see <https://www.gnu.org/licenses/>.
|
* this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
$overscroll-padding: 720px;
|
||||||
|
$parallax-strength: 2;
|
||||||
|
$default-parallax-content-size: 230px;
|
||||||
|
|
||||||
@mixin ion-content-parallax(
|
ion-content::part(parallax-scroll) {
|
||||||
$parallax-background: var(--ion-color-primary),
|
perspective: 2px;
|
||||||
$background: var(--ion-background-color),
|
perspective-origin: center top;
|
||||||
$parallax-strength: 2,
|
}
|
||||||
$overscroll-padding: 720px,
|
|
||||||
$content-size: 230px
|
ion-content::part(parallax-parent) {
|
||||||
) {
|
transform-style: preserve-3d;
|
||||||
&::part(background) {
|
position: relative;
|
||||||
background: $background;
|
}
|
||||||
}
|
|
||||||
&::part(scroll) {
|
ion-content::part(parallax) {
|
||||||
perspective: 2px;
|
position: absolute;
|
||||||
perspective-origin: center top;
|
top: 0;
|
||||||
}
|
right: 0;
|
||||||
> div {
|
left: 0;
|
||||||
transform-style: preserve-3d;
|
|
||||||
position: relative;
|
$translateY: calc($overscroll-padding * $parallax-strength);
|
||||||
|
$translateZ: calc(-1px * $parallax-strength);
|
||||||
&::before {
|
$transform-origin: calc($parallax-strength * $parallax-strength * $overscroll-padding);
|
||||||
content: ' ';
|
|
||||||
position: absolute;
|
height: calc(var(--parallax-content-size, $default-parallax-content-size) + $overscroll-padding);
|
||||||
top: 0;
|
width: 150%;
|
||||||
right: 0;
|
transform-origin: 50% $transform-origin;
|
||||||
left: 0;
|
transform: translate3d(0px, $translateY, $translateZ) scale($parallax-strength);
|
||||||
|
|
||||||
$height: calc($content-size + $overscroll-padding);
|
background: var(--parallax-background, var(--ion-color-primary));
|
||||||
$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: translate3d(0px, $translateY, $translateZ) scale($parallax-strength);
|
|
||||||
|
|
||||||
background: $parallax-background;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user