Update/misc UI fixes (#1961)

* fix login error message spacing and ensure not longer than 64 chars

* spinner color to tertiary

* totally responsive homepage cards

* copy changes, back button for marketplace, minor styling

* center setup wizard tiles; adjust external link style

* remove cert note from setup success

* convert launch card to go to login button

* change system settings to system; update icons

* refactor card widget input as full card details; more card resizing for home page

* cleanup

* clean up widget params

* delete contructor

Co-authored-by: Matt Hill <matthewonthemoon@gmail.com>
This commit is contained in:
Lucy C
2022-11-23 06:05:36 -07:00
committed by Aiden McClelland
parent 6eea2526f6
commit f7c5e64fbc
36 changed files with 451 additions and 322 deletions

View File

@@ -22,7 +22,7 @@ const routes: Routes = [
import('./pages/home/home.module').then(m => m.HomePageModule),
},
{
path: 'settings',
path: 'system',
canActivate: [AuthGuard],
canActivateChild: [AuthGuard],
loadChildren: () =>

View File

@@ -22,7 +22,7 @@
{{ page.title }}
</ion-label>
<ion-icon
*ngIf="page.url === '/settings' && (showEOSUpdate$ | async)"
*ngIf="page.url === '/system' && (showEOSUpdate$ | async)"
color="success"
size="small"
name="rocket-outline"

View File

@@ -38,9 +38,9 @@ export class MenuComponent {
icon: 'notifications-outline',
},
{
title: 'System Settings',
url: '/settings',
icon: 'settings-outline',
title: 'System',
url: '/system',
icon: 'construct-outline',
},
]

View File

@@ -54,6 +54,7 @@ const ICONS = [
'newspaper-outline',
'notifications-outline',
'open-outline',
'options-outline',
'pencil',
'phone-portrait-outline',
'play-circle-outline',

View File

@@ -11,7 +11,7 @@
<ng-container *ngTemplateOutlet="content"></ng-container>
</a>
<ng-template #internal>
<a [routerLink]="link">
<a [routerLink]="link" [queryParams]="qp">
<ng-container *ngTemplateOutlet="content"></ng-container>
</a>
</ng-template>

View File

@@ -13,7 +13,8 @@ import {
})
export class AnyLinkComponent implements OnInit {
@Input() link!: string
externalLink: boolean = false
@Input() qp?: Record<string, string>
externalLink = false
ngOnInit() {
try {

View File

@@ -1,15 +1,28 @@
<ion-card>
<any-link link="{{ link }}">
<div class="p1">
<ion-card-header>
<ion-card-title>{{ title }}</ion-card-title>
</ion-card-header>
<ion-card-content>
<ion-icon name="{{ icon }}" style="color: {{ color }}"></ion-icon>
</ion-card-content>
<ion-footer>
{{ description }}
</ion-footer>
</div>
</any-link>
</ion-card>
<div
class="outer-wrapper"
#outerWrapper
[ngStyle]="{ height: outerHeight, width: outerWidth }"
>
<div
class="inner-wrapper"
#innerWrapper
[ngStyle]="{ transform: innerTransform }"
>
<ion-card>
<any-link [link]="cardDetails.link" [qp]="cardDetails.qp">
<ion-card-header>
<ion-card-title>{{ cardDetails.title }}</ion-card-title>
</ion-card-header>
<ion-card-content>
<ion-icon
[name]="cardDetails.icon"
[style.color]="cardDetails.color"
></ion-icon>
</ion-card-content>
<ion-footer>
<p>{{ cardDetails.description }}</p>
</ion-footer>
</any-link>
</ion-card>
</div>
</div>

View File

@@ -3,7 +3,8 @@ ion-card {
box-shadow: 0px 4px 4px rgba(0, 0, 0, 0.25);
border-radius: 44px;
margin: auto;
max-width: 22rem;
max-height: 100%;
max-width: 100%;
text-align: center;
transition: all 350ms ease;
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
@@ -18,7 +19,8 @@ ion-card {
font-family: 'Open Sans';
padding: 0.6rem;
font-weight: 600;
font-size: 1.3rem;
font-size: calc(12px + 0.5vw);
height: 3rem;
}
ion-card-content {
@@ -29,7 +31,7 @@ ion-card {
align-items: center;
ion-icon {
font-size: 8rem;
font-size: calc(90px + 0.5vw);
--ionicon-stroke-width: 1rem;
}
}
@@ -37,7 +39,13 @@ ion-card {
ion-footer {
padding: 1rem;
font-family: 'Open Sans';
font-size: 1.2rem;
font-size: clamp(1rem, calc(12px + 0.5vw), 1.3rem);
height: 9rem;
width: clamp(13rem, 80%, 18rem);
margin: 0 auto;
* {
max-width: 100%;
}
}
.footer-md::before {
@@ -45,6 +53,17 @@ ion-card {
}
}
.p1 {
padding: 1.2rem;
@media (max-width: 900px) {
ion-card-title, ion-footer {
height: auto !important;
}
ion-footer {
width: 10rem;
}
}
@media (max-width: 1200px) {
ion-footer {
width: 14rem;
}
}

View File

@@ -1,4 +1,11 @@
import { ChangeDetectionStrategy, Component, Input } from '@angular/core'
import {
ChangeDetectionStrategy,
Component,
ElementRef,
HostListener,
Input,
ViewChild,
} from '@angular/core'
@Component({
selector: 'widget-card',
@@ -7,11 +14,53 @@ import { ChangeDetectionStrategy, Component, Input } from '@angular/core'
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class WidgetCardComponent {
@Input() title: string = ''
@Input() icon: string = ''
@Input() color: string = ''
@Input() description: string = ''
@Input() link: string = ''
@Input() cardDetails!: Card
@Input() containerDimensions!: Dimension
@ViewChild('outerWrapper') outerWrapper: ElementRef<HTMLElement> =
{} as ElementRef<HTMLElement>
@ViewChild('innerWrapper') innerWrapper: ElementRef<HTMLElement> =
{} as ElementRef<HTMLElement>
@HostListener('window:resize', ['$event'])
onResize() {
this.resize()
}
maxHeight = 0
maxWidth = 0
innerTransform = ''
outerWidth: any
outerHeight: any
constructor() {}
ngAfterViewInit() {
this.maxHeight = (<HTMLElement> (
this.innerWrapper.nativeElement
)).getBoundingClientRect().height
this.maxWidth = (<HTMLElement> (
this.innerWrapper.nativeElement
)).getBoundingClientRect().width
this.resize()
}
resize() {
const height = this.containerDimensions.height
const width = this.containerDimensions.width
const isMax = width >= this.maxWidth && height >= this.maxHeight
const scale = Math.min(width / this.maxWidth, height / this.maxHeight)
this.innerTransform = isMax ? '' : 'scale(' + scale + ')'
this.outerWidth = isMax ? '' : this.maxWidth * scale
this.outerHeight = isMax ? '' : this.maxHeight * scale
}
}
export interface Dimension {
height: number
width: number
}
export interface Card {
title: string
icon: string
color: string
description: string
link: string
qp?: Record<string, string>
}

View File

@@ -1,17 +1,18 @@
<ion-grid>
<ion-row class="ion-justify-content-center ion-align-items-center">
<ion-col
*ngFor="let card of cards"
size="auto"
class="ion-align-self-center"
>
<widget-card
title="{{ card.title }}"
icon="{{ card.icon }}"
color="{{ card.color }}"
description="{{ card.description }}"
link="{{ card.link }}"
></widget-card>
</ion-col>
</ion-row>
</ion-grid>
<div #gridContent>
<ion-grid>
<ion-row class="ion-justify-content-center ion-align-items-center">
<ion-col
*ngFor="let card of cards"
sizeLg="4"
sizeSm="6"
sizeXs="12"
class="ion-align-self-center"
>
<widget-card
[cardDetails]="card"
[containerDimensions]="containerDimensions"
></widget-card>
</ion-col>
</ion-row>
</ion-grid>
</div>

View File

@@ -1,7 +1,13 @@
ion-row {
grid-row-gap: 1rem;
ion-col {
max-width: 22rem !important;
--ion-grid-column-padding: 1rem;
}
ion-col {
padding: 0 0.5rem 0.5rem 1rem;
@media (min-width: 1800px) {
div {
padding: 0 20%;
}
ion-col {
max-width: 24rem !important;
}
}

View File

@@ -1,4 +1,11 @@
import { ChangeDetectionStrategy, Component, Input } from '@angular/core'
import {
ChangeDetectionStrategy,
Component,
ElementRef,
HostListener,
ViewChild,
} from '@angular/core'
import { Card, Dimension } from '../widget-card/widget-card.component'
@Component({
selector: 'widget-list',
@@ -7,7 +14,27 @@ import { ChangeDetectionStrategy, Component, Input } from '@angular/core'
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class WidgetListComponent {
constructor() {}
@ViewChild('gridContent') gridContent: ElementRef<HTMLElement> =
{} as ElementRef<HTMLElement>
@HostListener('window:resize', ['$event'])
onResize() {
this.setContainerDimensions()
}
containerDimensions: Dimension = {} as Dimension
ngAfterViewInit() {
this.setContainerDimensions()
}
setContainerDimensions() {
this.containerDimensions.height = (<HTMLElement> (
this.gridContent.nativeElement
)).getBoundingClientRect().height
this.containerDimensions.width = (<HTMLElement> (
this.gridContent.nativeElement
)).getBoundingClientRect().width
}
cards: Card[] = [
{
@@ -15,7 +42,8 @@ export class WidgetListComponent {
icon: 'storefront-outline',
color: 'var(--alt-blue)',
description: 'Shop for your favorite open source services',
link: '/marketplace/browse',
link: '/marketplace',
qp: { back: 'true' },
},
{
title: 'LAN Setup',
@@ -23,21 +51,21 @@ export class WidgetListComponent {
color: 'var(--alt-orange)',
description:
'Install your Embassy certificate for a secure local connection',
link: '/settings/lan',
link: '/system/lan',
},
{
title: 'Create Backup',
icon: 'duplicate-outline',
color: 'var(--alt-purple)',
description: 'Back up your Embassy and service data',
link: '/settings/backup',
link: '/system/backup',
},
{
title: 'Embassy Info',
icon: 'information-circle-outline',
color: 'var(--alt-green)',
description: 'View basic information about your Embassy',
link: '/settings/specs',
link: '/system/specs',
},
{
title: 'User Manual',
@@ -55,11 +83,3 @@ export class WidgetListComponent {
},
]
}
interface Card {
title: string
icon: string
color: string
description: string
link: string
}

View File

@@ -56,7 +56,7 @@ export class ToButtonsPipe implements PipeTransform {
this.modalService.presentModalConfig({ pkgId: pkg.manifest.id }),
title: 'Config',
description: `Customize ${pkgTitle}`,
icon: 'construct-outline',
icon: 'options-outline',
},
// properties
{

View File

@@ -7,7 +7,7 @@
</ion-toolbar>
</ion-header>
<ion-content>
<div style="padding: 36px 0">
<div class="padding-top">
<widget-list></widget-list>
</div>
</ion-content>

View File

@@ -0,0 +1,9 @@
.padding-top {
padding-top: 2rem;
}
@media (min-width: 2000px) {
.padding-top {
padding-top: 10rem;
}
}

View File

@@ -39,9 +39,6 @@
></ion-icon>
</ion-button>
</ion-item>
<p style="text-align: left; padding-top: 4px">
<ion-text color="danger">{{ error }}</ion-text>
</p>
</ion-item-group>
<ion-button
class="login-button"
@@ -51,6 +48,9 @@
>
<span style="font-size: larger; font-weight: bold">Login</span>
</ion-button>
<p style="text-align: left; padding-top: 4px">
<ion-text color="danger">{{ error }}</ion-text>
</p>
</form>
</ion-card-content>
</ion-card>

View File

@@ -28,6 +28,7 @@ ion-card {
background: var(--ion-color-step-200);
box-shadow: 0px 4px 4px rgba(0, 0, 0, 0.25);
border-radius: 44px;
min-height: 16rem;
}
.input-label {

View File

@@ -40,6 +40,10 @@ export class LoginPage {
try {
document.cookie = ''
if (this.password.length > 64) {
this.error = 'Password must be less than 65 characters'
return
}
await this.api.login({
password: this.password,
metadata: { platforms: getPlatforms() },

View File

@@ -1,5 +1,8 @@
<ion-header>
<ion-toolbar>
<ion-buttons slot="start" *ngIf="back">
<ion-back-button></ion-back-button>
</ion-buttons>
<ion-title>Marketplace</ion-title>
<ion-buttons slot="end">
<badge-menu-button></badge-menu-button>

View File

@@ -1,4 +1,5 @@
import { ChangeDetectionStrategy, Component, Inject } from '@angular/core'
import { ActivatedRoute } from '@angular/router'
import { ModalController } from '@ionic/angular'
import { AbstractMarketplaceService } from '@start9labs/marketplace'
import { PatchDB } from 'patch-db-client'
@@ -15,6 +16,8 @@ import { DataModel } from 'src/app/services/patch-db/data-model'
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class MarketplaceListPage {
readonly back = !!this.route.snapshot.queryParamMap.get('back')
readonly store$ = this.marketplaceService.getSelectedStore$().pipe(
filter(Boolean),
map(({ info, packages }) => {
@@ -73,6 +76,7 @@ export class MarketplaceListPage {
private readonly marketplaceService: MarketplaceService,
private readonly modalCtrl: ModalController,
private readonly config: ConfigService,
private readonly route: ActivatedRoute,
) {}
category = 'featured'

View File

@@ -4,11 +4,7 @@ import { Routes, RouterModule } from '@angular/router'
const routes: Routes = [
{
path: '',
redirectTo: 'browse',
pathMatch: 'full',
},
{
path: 'browse',
loadChildren: () =>
import('./marketplace-list/marketplace-list.module').then(
m => m.MarketplaceListPageModule,

View File

@@ -26,7 +26,7 @@ export class NotificationsPage {
notifications: ServerNotifications = []
beforeCursor?: number
needInfinite = false
fromToast = false
fromToast = !!this.route.snapshot.queryParamMap.get('toast')
readonly perPage = 40
readonly packageData$ = this.patch.watch$('package-data')
@@ -41,7 +41,6 @@ export class NotificationsPage {
) {}
async ngOnInit() {
this.fromToast = !!this.route.snapshot.queryParamMap.get('toast')
this.notifications = await this.getNotifications()
this.loading = false
}

View File

@@ -1,6 +1,6 @@
<ion-header>
<ion-toolbar>
<ion-title> System Settings </ion-title>
<ion-title> System </ion-title>
<ion-buttons slot="end">
<badge-menu-button></badge-menu-button>
</ion-buttons>