mirror of
https://gitlab.com/openstapps/openstapps.git
synced 2026-01-20 00:23:03 +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 -->
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
<ion-content class="ion-content-parallax">
|
||||
<div>
|
||||
<div class="about-changelog">
|
||||
<markdown src="assets/about/CHANGELOG.md"></markdown>
|
||||
</div>
|
||||
<ion-content parallax>
|
||||
<div class="about-changelog">
|
||||
<markdown src="assets/about/CHANGELOG.md"></markdown>
|
||||
</div>
|
||||
</ion-content>
|
||||
|
||||
@@ -22,32 +22,30 @@
|
||||
<!-- TODO: translation -->
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
<ion-content class="ion-content-parallax">
|
||||
<div>
|
||||
<div class="licenses-content">
|
||||
<ion-card
|
||||
*ngFor="let license of licenses"
|
||||
[href]="license.url || license.repository"
|
||||
rel="external"
|
||||
target="_blank"
|
||||
>
|
||||
<ion-card-header>
|
||||
<ion-card-title>
|
||||
{{ license.name }}
|
||||
<ion-icon size="16" weight="300" class="supertext-icon" name="open_in_browser"></ion-icon>
|
||||
</ion-card-title>
|
||||
<ion-content parallax>
|
||||
<div class="licenses-content">
|
||||
<ion-card
|
||||
*ngFor="let license of licenses"
|
||||
[href]="license.url || license.repository"
|
||||
rel="external"
|
||||
target="_blank"
|
||||
>
|
||||
<ion-card-header>
|
||||
<ion-card-title>
|
||||
{{ license.name }}
|
||||
<ion-icon size="16" weight="300" class="supertext-icon" name="open_in_browser"></ion-icon>
|
||||
</ion-card-title>
|
||||
|
||||
<ion-card-subtitle *ngIf="license.authors || license.publisher">
|
||||
{{ license.authors || license.publisher }}
|
||||
</ion-card-subtitle>
|
||||
</ion-card-header>
|
||||
<ion-card-content>
|
||||
<ion-chip (click)="$event.preventDefault(); viewLicense(license)">
|
||||
<ion-icon name="copyright"></ion-icon>
|
||||
<ion-label>{{ license.licenses }} License</ion-label>
|
||||
</ion-chip>
|
||||
</ion-card-content>
|
||||
</ion-card>
|
||||
</div>
|
||||
<ion-card-subtitle *ngIf="license.authors || license.publisher">
|
||||
{{ license.authors || license.publisher }}
|
||||
</ion-card-subtitle>
|
||||
</ion-card-header>
|
||||
<ion-card-content>
|
||||
<ion-chip (click)="$event.preventDefault(); viewLicense(license)">
|
||||
<ion-icon name="copyright"></ion-icon>
|
||||
<ion-label>{{ license.licenses }} License</ion-label>
|
||||
</ion-chip>
|
||||
</ion-card-content>
|
||||
</ion-card>
|
||||
</div>
|
||||
</ion-content>
|
||||
|
||||
@@ -24,11 +24,9 @@
|
||||
</ng-template>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
<ion-content *ngIf="content" class="ion-content-parallax">
|
||||
<div>
|
||||
<ion-text>{{ appName }} v{{ version }}</ion-text>
|
||||
<div class="page-content">
|
||||
<about-page-content *ngFor="let element of content.content" [content]="element"></about-page-content>
|
||||
</div>
|
||||
<ion-content parallax *ngIf="content">
|
||||
<ion-text>{{ appName }} v{{ version }}</ion-text>
|
||||
<div class="page-content">
|
||||
<about-page-content *ngFor="let element of content.content" [content]="element"></about-page-content>
|
||||
</div>
|
||||
</ion-content>
|
||||
|
||||
@@ -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
|
||||
~ under the terms of the GNU General Public License as published by the Free
|
||||
~ Software Foundation, version 3.
|
||||
@@ -26,14 +26,12 @@
|
||||
<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 parallax [parallaxSize]="160" class="ion-padding modal-content">
|
||||
<stapps-edit-event-selection
|
||||
#selection
|
||||
[items]="associatedDateSeries"
|
||||
(modified)="editModal.pendingChanges = true"
|
||||
></stapps-edit-event-selection>
|
||||
</ion-content>
|
||||
<ion-footer mode="ios">
|
||||
<ion-toolbar color="light">
|
||||
|
||||
@@ -12,8 +12,6 @@
|
||||
* 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);
|
||||
@@ -35,10 +33,7 @@
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
--background: var(--ion-color-primary);
|
||||
--color: var(--ion-color-primary-contrast);
|
||||
|
||||
@include ion-content-parallax($content-size: 160px);
|
||||
}
|
||||
|
||||
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
|
||||
~ under the terms of the GNU General Public License as published by the Free
|
||||
~ Software Foundation, version 3.
|
||||
@@ -30,8 +30,8 @@
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
<ng-content select="[header]"></ng-content>
|
||||
<ion-content class="ion-no-padding ion-content-parallax">
|
||||
<div [ngSwitch]="true">
|
||||
<ion-content parallax class="ion-no-padding">
|
||||
<ng-container [ngSwitch]="true">
|
||||
<ng-container *ngSwitchCase="!item && (isDisconnected | async)">
|
||||
<div class="centeredMessageContainer">
|
||||
<ion-icon name="signal_disconnected"></ion-icon>
|
||||
@@ -59,5 +59,5 @@
|
||||
[contentTemplateRef]="contentTemplateRef"
|
||||
></stapps-data-detail-content>
|
||||
</ng-container>
|
||||
</div>
|
||||
</ng-container>
|
||||
</ion-content>
|
||||
|
||||
@@ -12,11 +12,6 @@
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
:host {
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
.crumb-label {
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
|
||||
@@ -21,107 +21,105 @@
|
||||
<ion-title>{{ 'feedback.page.TITLE' | translate }}</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
<ion-content class="ion-content-parallax">
|
||||
<div>
|
||||
<div class="feedback-content">
|
||||
<ion-card>
|
||||
<form #feedbackForm="ngForm" (ngSubmit)="onSubmit()">
|
||||
<ion-item>
|
||||
<ion-label position="stacked">{{ 'feedback.form.name.label' | translate }}</ion-label>
|
||||
<ion-input
|
||||
placeholder="{{ 'feedback.form.name.placeholder' | translate }}"
|
||||
[(ngModel)]="author.name"
|
||||
name="name"
|
||||
></ion-input>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label position="stacked">{{ 'feedback.form.type.label' | translate }}</ion-label>
|
||||
<ion-select
|
||||
[(ngModel)]="message.name"
|
||||
value="comment"
|
||||
name="title"
|
||||
interface="popover"
|
||||
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
|
||||
<ion-content parallax>
|
||||
<div class="feedback-content">
|
||||
<ion-card>
|
||||
<form #feedbackForm="ngForm" (ngSubmit)="onSubmit()">
|
||||
<ion-item>
|
||||
<ion-label position="stacked">{{ 'feedback.form.name.label' | translate }}</ion-label>
|
||||
<ion-input
|
||||
placeholder="{{ 'feedback.form.name.placeholder' | translate }}"
|
||||
[(ngModel)]="author.name"
|
||||
name="name"
|
||||
></ion-input>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label position="stacked">{{ 'feedback.form.type.label' | translate }}</ion-label>
|
||||
<ion-select
|
||||
[(ngModel)]="message.name"
|
||||
value="comment"
|
||||
name="title"
|
||||
interface="popover"
|
||||
required="true"
|
||||
>
|
||||
</form>
|
||||
</ion-card>
|
||||
</div>
|
||||
<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-card>
|
||||
</div>
|
||||
</ion-content>
|
||||
|
||||
@@ -22,41 +22,39 @@
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
|
||||
<ion-content class="ion-content-parallax" (elementSizeChange)="calcPageSize($event)">
|
||||
<div>
|
||||
<ion-refresher slot="fixed" (ionRefresh)="refresh($event.target)">
|
||||
<ion-refresher-content
|
||||
pullingIcon="chevron-down-outline"
|
||||
pullingText="{{ 'data.REFRESH_ACTION' | translate }}"
|
||||
refreshingText="{{ 'data.REFRESHING' | translate }}"
|
||||
refreshingSpinner="crescent"
|
||||
>
|
||||
</ion-refresher-content>
|
||||
</ion-refresher>
|
||||
<ion-grid>
|
||||
<ion-row>
|
||||
<ion-col size="12">
|
||||
<stapps-news-settings-filter
|
||||
*ngIf="settings"
|
||||
[settings]="settings"
|
||||
(filtersChanged)="toggleFilter($event)"
|
||||
></stapps-news-settings-filter>
|
||||
</ion-col>
|
||||
</ion-row>
|
||||
</ion-grid>
|
||||
<div class="news-grid">
|
||||
<ng-container *ngIf="!news">
|
||||
<stapps-skeleton-news-item *ngFor="let skeleton of [1, 2, 3, 4, 5]"></stapps-skeleton-news-item>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="news.length > 0">
|
||||
<stapps-news-item *ngFor="let item of news" [item]="item"></stapps-news-item>
|
||||
</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>
|
||||
<ion-content parallax (elementSizeChange)="calcPageSize($event)">
|
||||
<ion-refresher slot="fixed" (ionRefresh)="refresh($event.target)">
|
||||
<ion-refresher-content
|
||||
pullingIcon="chevron-down-outline"
|
||||
pullingText="{{ 'data.REFRESH_ACTION' | translate }}"
|
||||
refreshingText="{{ 'data.REFRESHING' | translate }}"
|
||||
refreshingSpinner="crescent"
|
||||
>
|
||||
</ion-refresher-content>
|
||||
</ion-refresher>
|
||||
<ion-grid>
|
||||
<ion-row>
|
||||
<ion-col size="12">
|
||||
<stapps-news-settings-filter
|
||||
*ngIf="settings"
|
||||
[settings]="settings"
|
||||
(filtersChanged)="toggleFilter($event)"
|
||||
></stapps-news-settings-filter>
|
||||
</ion-col>
|
||||
</ion-row>
|
||||
</ion-grid>
|
||||
<div class="news-grid">
|
||||
<ng-container *ngIf="!news">
|
||||
<stapps-skeleton-news-item *ngFor="let skeleton of [1, 2, 3, 4, 5]"></stapps-skeleton-news-item>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="news.length > 0">
|
||||
<stapps-news-item *ngFor="let item of news" [item]="item"></stapps-news-item>
|
||||
</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>
|
||||
</ion-content>
|
||||
|
||||
@@ -22,101 +22,97 @@
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
|
||||
<ion-content>
|
||||
<div>
|
||||
<section class="user-card-wrapper">
|
||||
<ion-card class="user-card">
|
||||
<ion-card-header>
|
||||
<ion-img src="assets/imgs/header.svg"></ion-img>
|
||||
<span *ngIf="user$ | async as userInfo">
|
||||
{{
|
||||
userInfo.role ? (userInfo?.role | titlecase) : ('profile.role_guest' | translate | titlecase)
|
||||
}}
|
||||
</span>
|
||||
</ion-card-header>
|
||||
<ion-card-content>
|
||||
<ion-img class="profile-card-img" src="assets/imgs/profile-card-head.svg"></ion-img>
|
||||
<ion-grid>
|
||||
<ion-row>
|
||||
<ion-col size="3"></ion-col>
|
||||
<ion-col
|
||||
*ngIf="data.default.loggedIn$ | async as loggedIn; else logInPrompt"
|
||||
size="9"
|
||||
class="main-info"
|
||||
>
|
||||
<ng-container *ngIf="user$ | async as userInfo">
|
||||
<ion-text class="full-name">
|
||||
{{ userInfo?.name }}
|
||||
<ion-content color="light" parallax [parallaxSize]="130">
|
||||
<section class="user-card-wrapper">
|
||||
<ion-card class="user-card">
|
||||
<ion-card-header>
|
||||
<ion-img src="assets/imgs/header.svg"></ion-img>
|
||||
<span *ngIf="user$ | async as userInfo">
|
||||
{{ userInfo.role ? (userInfo?.role | titlecase) : ('profile.role_guest' | translate | titlecase) }}
|
||||
</span>
|
||||
</ion-card-header>
|
||||
<ion-card-content>
|
||||
<ion-img class="profile-card-img" src="assets/imgs/profile-card-head.svg"></ion-img>
|
||||
<ion-grid>
|
||||
<ion-row>
|
||||
<ion-col size="3"></ion-col>
|
||||
<ion-col
|
||||
*ngIf="data.default.loggedIn$ | async as loggedIn; else logInPrompt"
|
||||
size="9"
|
||||
class="main-info"
|
||||
>
|
||||
<ng-container *ngIf="user$ | async as userInfo">
|
||||
<ion-text class="full-name">
|
||||
{{ userInfo?.name }}
|
||||
</ion-text>
|
||||
<div class="matriculation-number">
|
||||
<ion-label>
|
||||
{{ 'profile.userInfo.studentId' | translate | uppercase }}
|
||||
</ion-label>
|
||||
<ion-text>
|
||||
{{ userInfo?.studentId }}
|
||||
</ion-text>
|
||||
<div class="matriculation-number">
|
||||
<ion-label>
|
||||
{{ 'profile.userInfo.studentId' | translate | uppercase }}
|
||||
</ion-label>
|
||||
<ion-text>
|
||||
{{ userInfo?.studentId }}
|
||||
</ion-text>
|
||||
</div>
|
||||
<div class="user-name">
|
||||
<ion-label>
|
||||
{{ 'profile.userInfo.username' | translate | uppercase }}
|
||||
</ion-label>
|
||||
<ion-text>{{ userInfo?.id }}</ion-text>
|
||||
</div>
|
||||
<div class="email">
|
||||
<ion-label>
|
||||
{{ 'profile.userInfo.email' | translate | uppercase }}
|
||||
</ion-label>
|
||||
<ion-text>
|
||||
{{ userInfo?.email }}
|
||||
</ion-text>
|
||||
</div>
|
||||
</ng-container>
|
||||
</div>
|
||||
<div class="user-name">
|
||||
<ion-label>
|
||||
{{ 'profile.userInfo.username' | translate | uppercase }}
|
||||
</ion-label>
|
||||
<ion-text>{{ userInfo?.id }}</ion-text>
|
||||
</div>
|
||||
<div class="email">
|
||||
<ion-label>
|
||||
{{ 'profile.userInfo.email' | translate | uppercase }}
|
||||
</ion-label>
|
||||
<ion-text>
|
||||
{{ userInfo?.email }}
|
||||
</ion-text>
|
||||
</div>
|
||||
</ng-container>
|
||||
</ion-col>
|
||||
<ng-template #logInPrompt>
|
||||
<ion-col size="9">
|
||||
<ion-text class="log-in-prompt">
|
||||
{{ 'profile.userInfo.logInPrompt' | translate }}
|
||||
</ion-text>
|
||||
</ion-col>
|
||||
<ng-template #logInPrompt>
|
||||
<ion-col size="9">
|
||||
<ion-text class="log-in-prompt">
|
||||
{{ 'profile.userInfo.logInPrompt' | translate }}
|
||||
</ion-text>
|
||||
</ion-col>
|
||||
</ng-template>
|
||||
</ion-row>
|
||||
</ion-grid>
|
||||
</ion-card-content>
|
||||
</ion-card>
|
||||
</section>
|
||||
<stapps-profile-page-section
|
||||
*ngFor="let section of sections"
|
||||
[item]="section"
|
||||
></stapps-profile-page-section>
|
||||
<section class="courses">
|
||||
<ion-label class="section-headline">
|
||||
{{ 'profile.titleCourses' | translate | uppercase }}
|
||||
</ion-label>
|
||||
<ion-card class="courses-card">
|
||||
<ion-card-header (click)="toggleCourseCardState()">
|
||||
<span>{{ 'profile.courses.today' | translate | uppercase }}</span>
|
||||
<ion-icon [name]="courseCardState" color="dark" size="20"></ion-icon>
|
||||
</ion-card-header>
|
||||
<ion-card-content class="course-card" [class.show-card]="courseCardState === courseCardEnum.expanded">
|
||||
<ng-container *ngIf="myCoursesToday.length === 0">
|
||||
<div class="no-course">
|
||||
{{ 'profile.courses.no_courses' | translate }}
|
||||
</ng-template>
|
||||
</ion-row>
|
||||
</ion-grid>
|
||||
</ion-card-content>
|
||||
</ion-card>
|
||||
</section>
|
||||
<stapps-profile-page-section
|
||||
*ngFor="let section of sections"
|
||||
[item]="section"
|
||||
></stapps-profile-page-section>
|
||||
<section class="courses">
|
||||
<ion-label class="section-headline">
|
||||
{{ 'profile.titleCourses' | translate | uppercase }}
|
||||
</ion-label>
|
||||
<ion-card class="courses-card">
|
||||
<ion-card-header (click)="toggleCourseCardState()">
|
||||
<span>{{ 'profile.courses.today' | translate | uppercase }}</span>
|
||||
<ion-icon [name]="courseCardState" color="dark" size="20"></ion-icon>
|
||||
</ion-card-header>
|
||||
<ion-card-content class="course-card" [class.show-card]="courseCardState === courseCardEnum.expanded">
|
||||
<ng-container *ngIf="myCoursesToday.length === 0">
|
||||
<div class="no-course">
|
||||
{{ 'profile.courses.no_courses' | translate }}
|
||||
</div>
|
||||
</ng-container>
|
||||
<ng-container *ngFor="let myCourse of myCoursesToday">
|
||||
<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>
|
||||
</ng-container>
|
||||
<ng-container *ngFor="let myCourse of myCoursesToday">
|
||||
<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 *ngIf="myCourse.course?.inPlace?.name" [class.last]="myCourse.course?.inPlace?.name">
|
||||
{{ myCourse.course?.inPlace.name }}
|
||||
</div>
|
||||
</ng-container>
|
||||
</ion-card-content>
|
||||
</ion-card>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</ng-container>
|
||||
</ion-card-content>
|
||||
</ion-card>
|
||||
</section>
|
||||
</ion-content>
|
||||
|
||||
@@ -12,15 +12,7 @@
|
||||
* 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 {
|
||||
ion-content {
|
||||
--background: var(--ion-color-light);
|
||||
|
||||
@include ion-content-parallax($content-size: 130px);
|
||||
}
|
||||
|
||||
section {
|
||||
margin-bottom: calc(2 * var(--spacing-lg) - var(--spacing-md));
|
||||
padding: var(--spacing-md);
|
||||
|
||||
@@ -22,12 +22,11 @@
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
|
||||
<ion-content class="ion-no-padding ion-content-parallax">
|
||||
<div>
|
||||
<div class="settings-content">
|
||||
<ng-container *ngFor="let categoryKey of categoriesOrder">
|
||||
<ion-list *ngIf="objectKeys(settingsCache).includes(categoryKey)">
|
||||
<!-- <ion-item-divider>
|
||||
<ion-content parallax class="ion-no-padding">
|
||||
<div class="settings-content">
|
||||
<ng-container *ngFor="let categoryKey of categoriesOrder">
|
||||
<ion-list *ngIf="objectKeys(settingsCache).includes(categoryKey)">
|
||||
<!-- <ion-item-divider>
|
||||
<h2>
|
||||
{{
|
||||
'categories[0]'
|
||||
@@ -41,19 +40,18 @@
|
||||
}}
|
||||
</h2>
|
||||
</ion-item-divider> -->
|
||||
<stapps-settings-item
|
||||
*ngFor="let settingKeys of objectKeys(settingsCache[categoryKey].settings)"
|
||||
[setting]="settingsCache[categoryKey].settings[settingKeys]"
|
||||
></stapps-settings-item>
|
||||
</ion-list>
|
||||
</ng-container>
|
||||
<stapps-settings-item
|
||||
*ngFor="let settingKeys of objectKeys(settingsCache[categoryKey].settings)"
|
||||
[setting]="settingsCache[categoryKey].settings[settingKeys]"
|
||||
></stapps-settings-item>
|
||||
</ion-list>
|
||||
</ng-container>
|
||||
|
||||
<calendar-sync-settings></calendar-sync-settings>
|
||||
<calendar-sync-settings></calendar-sync-settings>
|
||||
|
||||
<ion-button expand="block" (click)="presentResetAlert()" fill="outline">
|
||||
{{ 'settings.resetSettings' | translate }}
|
||||
<ion-icon slot="start" name="device_reset"></ion-icon>
|
||||
</ion-button>
|
||||
</div>
|
||||
<ion-button expand="block" (click)="presentResetAlert()" fill="outline">
|
||||
{{ 'settings.resetSettings' | translate }}
|
||||
<ion-icon slot="start" name="device_reset"></ion-icon>
|
||||
</ion-button>
|
||||
</div>
|
||||
</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 {SectionComponent} from './section.component';
|
||||
import {RouterModule} from '@angular/router';
|
||||
import {IonContentParallaxDirective} from './ion-content-parallax.directive';
|
||||
|
||||
@NgModule({
|
||||
imports: [BrowserModule, IonicModule, TranslateModule, ThingTranslateModule.forChild(), RouterModule],
|
||||
declarations: [
|
||||
IonContentParallaxDirective,
|
||||
ElementSizeChangeDirective,
|
||||
ArrayLastPipe,
|
||||
DateIsThisPipe,
|
||||
@@ -50,6 +52,7 @@ import {RouterModule} from '@angular/router';
|
||||
SearchbarAutofocusDirective,
|
||||
],
|
||||
exports: [
|
||||
IonContentParallaxDirective,
|
||||
ElementSizeChangeDirective,
|
||||
ArrayLastPipe,
|
||||
DateIsThisPipe,
|
||||
|
||||
@@ -124,10 +124,6 @@ ion-header {
|
||||
}
|
||||
}
|
||||
|
||||
.ion-content-parallax {
|
||||
@include ion-content-parallax();
|
||||
}
|
||||
|
||||
ion-alert {
|
||||
button.alert-button.preferred {
|
||||
background-color: var(--ion-color-primary);
|
||||
|
||||
@@ -12,43 +12,34 @@
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* 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(
|
||||
$parallax-background: var(--ion-color-primary),
|
||||
$background: var(--ion-background-color),
|
||||
$parallax-strength: 2,
|
||||
$overscroll-padding: 720px,
|
||||
$content-size: 230px
|
||||
) {
|
||||
&::part(background) {
|
||||
background: $background;
|
||||
}
|
||||
&::part(scroll) {
|
||||
perspective: 2px;
|
||||
perspective-origin: center top;
|
||||
}
|
||||
> div {
|
||||
transform-style: preserve-3d;
|
||||
position: relative;
|
||||
|
||||
&::before {
|
||||
content: ' ';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
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: translate3d(0px, $translateY, $translateZ) scale($parallax-strength);
|
||||
|
||||
background: $parallax-background;
|
||||
}
|
||||
}
|
||||
ion-content::part(parallax-scroll) {
|
||||
perspective: 2px;
|
||||
perspective-origin: center top;
|
||||
}
|
||||
|
||||
ion-content::part(parallax-parent) {
|
||||
transform-style: preserve-3d;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
ion-content::part(parallax) {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
left: 0;
|
||||
|
||||
$translateY: calc($overscroll-padding * $parallax-strength);
|
||||
$translateZ: calc(-1px * $parallax-strength);
|
||||
$transform-origin: calc($parallax-strength * $parallax-strength * $overscroll-padding);
|
||||
|
||||
height: calc(var(--parallax-content-size, $default-parallax-content-size) + $overscroll-padding);
|
||||
width: 150%;
|
||||
transform-origin: 50% $transform-origin;
|
||||
transform: translate3d(0px, $translateY, $translateZ) scale($parallax-strength);
|
||||
|
||||
background: var(--parallax-background, var(--ion-color-primary));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user