mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-04-04 14:29:45 +00:00
refactor: decompose app component
This commit is contained in:
@@ -3,7 +3,9 @@
|
|||||||
<img alt="" [src]="'data:image/png;base64,' + pkg.icon | trustUrl" />
|
<img alt="" [src]="'data:image/png;base64,' + pkg.icon | trustUrl" />
|
||||||
</ion-thumbnail>
|
</ion-thumbnail>
|
||||||
<ion-label>
|
<ion-label>
|
||||||
<h2 class="pkg-title">{{ pkg.manifest.title }}</h2>
|
<h2 class="montserrat">
|
||||||
|
<strong>{{ pkg.manifest.title }}</strong>
|
||||||
|
</h2>
|
||||||
<h3>{{ pkg.manifest.description.short }}</h3>
|
<h3>{{ pkg.manifest.description.short }}</h3>
|
||||||
<ng-content></ng-content>
|
<ng-content></ng-content>
|
||||||
</ion-label>
|
</ion-label>
|
||||||
|
|||||||
@@ -1,4 +0,0 @@
|
|||||||
.pkg-title {
|
|
||||||
font-family: 'Montserrat', sans-serif;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
@@ -5,7 +5,6 @@ import { MarketplacePkg } from '../../../types/marketplace-pkg'
|
|||||||
@Component({
|
@Component({
|
||||||
selector: 'marketplace-item',
|
selector: 'marketplace-item',
|
||||||
templateUrl: 'item.component.html',
|
templateUrl: 'item.component.html',
|
||||||
styleUrls: ['item.component.scss'],
|
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
})
|
})
|
||||||
export class ItemComponent {
|
export class ItemComponent {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<ion-grid>
|
<ion-grid>
|
||||||
<ion-row>
|
<ion-row>
|
||||||
<ion-col sizeXs="12" sizeMd="9">
|
<ion-col sizeXs="12" sizeSm="12" sizeMd="9" sizeLg="9" sizeXl="9">
|
||||||
<div class="header">
|
<div class="header montserrat">
|
||||||
<img
|
<img
|
||||||
class="logo"
|
class="logo"
|
||||||
alt=""
|
alt=""
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
.header {
|
.header {
|
||||||
font-family: 'Montserrat', sans-serif;
|
|
||||||
padding: 2%;
|
padding: 2%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -83,3 +83,7 @@ ion-modal::part(content) {
|
|||||||
animation-fill-mode: forwards;
|
animation-fill-mode: forwards;
|
||||||
width: 1em;
|
width: 1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.montserrat {
|
||||||
|
font-family: 'Montserrat', sans-serif;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,300 +1,21 @@
|
|||||||
<ion-app>
|
<ion-app>
|
||||||
<ion-content>
|
<ion-content>
|
||||||
<ion-split-pane
|
<ion-split-pane
|
||||||
|
contentId="main-content"
|
||||||
[disabled]="!showMenu"
|
[disabled]="!showMenu"
|
||||||
(ionSplitPaneVisible)="splitPaneVisible($event)"
|
(ionSplitPaneVisible)="splitPaneVisible($event)"
|
||||||
contentId="main-content"
|
|
||||||
>
|
>
|
||||||
<ion-menu contentId="main-content" type="overlay">
|
<ion-menu contentId="main-content" type="overlay">
|
||||||
<ion-content color="light" scrollY="false">
|
<ion-content color="light" scrollY="false">
|
||||||
<div style="text-align: center" class="ion-padding">
|
<app-menu></app-menu>
|
||||||
<img
|
|
||||||
style="width: 45%; cursor: pointer"
|
|
||||||
src="assets/img/logo.png"
|
|
||||||
(click)="goToWebsite()"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div class="divider"></div>
|
|
||||||
<ion-item-group style="padding: 30px 0px">
|
|
||||||
<ion-menu-toggle
|
|
||||||
auto-hide="false"
|
|
||||||
*ngFor="let page of appPages; let i = index"
|
|
||||||
>
|
|
||||||
<ion-item
|
|
||||||
style="padding-left: 10px"
|
|
||||||
color="transparent"
|
|
||||||
button
|
|
||||||
(click)="selectedIndex = i"
|
|
||||||
routerDirection="root"
|
|
||||||
[routerLink]="[page.url]"
|
|
||||||
lines="none"
|
|
||||||
detail="false"
|
|
||||||
*ngIf="
|
|
||||||
page.url !== '/developer' ||
|
|
||||||
(localStorageService.showDevTools$ | async)
|
|
||||||
"
|
|
||||||
>
|
|
||||||
<ion-icon
|
|
||||||
slot="start"
|
|
||||||
[name]="page.icon"
|
|
||||||
[class]="selectedIndex === i ? 'bold' : 'dim'"
|
|
||||||
></ion-icon>
|
|
||||||
<ion-label
|
|
||||||
style="font-family: 'Montserrat'"
|
|
||||||
[class]="selectedIndex === i ? 'bold' : 'dim'"
|
|
||||||
>
|
|
||||||
{{ page.title }}
|
|
||||||
</ion-label>
|
|
||||||
<ng-container *ngIf="page.url === '/embassy'">
|
|
||||||
<ion-icon
|
|
||||||
*ngIf="eosService.updateAvailable$ | async"
|
|
||||||
color="success"
|
|
||||||
size="small"
|
|
||||||
name="rocket-outline"
|
|
||||||
></ion-icon>
|
|
||||||
</ng-container>
|
|
||||||
<ion-badge
|
|
||||||
*ngIf="page.url === '/notifications' && unreadCount"
|
|
||||||
color="danger"
|
|
||||||
style="margin-right: 3%"
|
|
||||||
>{{ unreadCount }}</ion-badge
|
|
||||||
>
|
|
||||||
</ion-item>
|
|
||||||
</ion-menu-toggle>
|
|
||||||
</ion-item-group>
|
|
||||||
<img
|
|
||||||
(click)="openSnek()"
|
|
||||||
style="
|
|
||||||
cursor: pointer;
|
|
||||||
position: absolute;
|
|
||||||
bottom: 90px;
|
|
||||||
left: 20px;
|
|
||||||
width: 20px;
|
|
||||||
"
|
|
||||||
src="assets/img/icons/snek.png"
|
|
||||||
/>
|
|
||||||
<div
|
|
||||||
style="
|
|
||||||
text-align: center;
|
|
||||||
height: 75px;
|
|
||||||
position: absolute;
|
|
||||||
bottom: 0;
|
|
||||||
right: 0;
|
|
||||||
width: 100%;
|
|
||||||
"
|
|
||||||
>
|
|
||||||
<div class="divider" style="margin-bottom: 10px"></div>
|
|
||||||
<ion-menu-toggle auto-hide="false">
|
|
||||||
<ion-item
|
|
||||||
button
|
|
||||||
lines="none"
|
|
||||||
style="
|
|
||||||
--background: transparent;
|
|
||||||
margin-bottom: 86px;
|
|
||||||
text-align: center;
|
|
||||||
"
|
|
||||||
fill="clear"
|
|
||||||
(click)="presentAlertLogout()"
|
|
||||||
>
|
|
||||||
<ion-label
|
|
||||||
><ion-text style="font-family: 'Montserrat'" color="dark">
|
|
||||||
Log Out
|
|
||||||
</ion-text></ion-label
|
|
||||||
>
|
|
||||||
</ion-item>
|
|
||||||
</ion-menu-toggle>
|
|
||||||
</div>
|
|
||||||
</ion-content>
|
</ion-content>
|
||||||
</ion-menu>
|
</ion-menu>
|
||||||
<ion-router-outlet id="main-content"></ion-router-outlet>
|
<ion-router-outlet id="main-content"></ion-router-outlet>
|
||||||
</ion-split-pane>
|
</ion-split-pane>
|
||||||
|
|
||||||
<section id="preload" style="display: none">
|
<section appPreloader></section>
|
||||||
<!-- 3rd party components -->
|
|
||||||
<qr-code value="hello"></qr-code>
|
|
||||||
|
|
||||||
<!-- Ionicons -->
|
|
||||||
<ion-icon name="add"></ion-icon>
|
|
||||||
<ion-icon name="alert-outline"></ion-icon>
|
|
||||||
<ion-icon name="alert-circle-outline"></ion-icon>
|
|
||||||
<ion-icon name="aperture-outline"></ion-icon>
|
|
||||||
<ion-icon name="arrow-back"></ion-icon>
|
|
||||||
<ion-icon name="arrow-forward"></ion-icon>
|
|
||||||
<ion-icon name="arrow-up"></ion-icon>
|
|
||||||
<ion-icon name="briefcase-outline"></ion-icon>
|
|
||||||
<ion-icon name="bookmark-outline"></ion-icon>
|
|
||||||
<ion-icon name="cellular-outline"></ion-icon>
|
|
||||||
<ion-icon name="chatbubbles-outline"></ion-icon>
|
|
||||||
<ion-icon name="checkmark"></ion-icon>
|
|
||||||
<ion-icon name="chevron-down"></ion-icon>
|
|
||||||
<ion-icon name="chevron-up"></ion-icon>
|
|
||||||
<!-- needed for detail="true" on ion-item button -->
|
|
||||||
<ion-icon name="chevron-forward"></ion-icon>
|
|
||||||
<ion-icon name="close"></ion-icon>
|
|
||||||
<ion-icon name="cloud-outline"></ion-icon>
|
|
||||||
<ion-icon name="cloud-done-outline"></ion-icon>
|
|
||||||
<ion-icon name="cloud-download-outline"></ion-icon>
|
|
||||||
<ion-icon name="cloud-offline-outline"></ion-icon>
|
|
||||||
<ion-icon name="cloud-upload-outline"></ion-icon>
|
|
||||||
<ion-icon name="code-outline"></ion-icon>
|
|
||||||
<ion-icon name="cog-outline"></ion-icon>
|
|
||||||
<ion-icon name="color-wand-outline"></ion-icon>
|
|
||||||
<ion-icon name="construct-outline"></ion-icon>
|
|
||||||
<ion-icon name="copy-outline"></ion-icon>
|
|
||||||
<ion-icon name="cube-outline"></ion-icon>
|
|
||||||
<ion-icon name="desktop-outline"></ion-icon>
|
|
||||||
<ion-icon name="download-outline"></ion-icon>
|
|
||||||
<ion-icon name="earth-outline"></ion-icon>
|
|
||||||
<ion-icon name="ellipsis-horizontal-outline"></ion-icon>
|
|
||||||
<ion-icon name="eye-off-outline"></ion-icon>
|
|
||||||
<ion-icon name="eye-outline"></ion-icon>
|
|
||||||
<ion-icon name="file-tray-stacked-outline"></ion-icon>
|
|
||||||
<ion-icon name="finger-print-outline"></ion-icon>
|
|
||||||
<ion-icon name="flash-outline"></ion-icon>
|
|
||||||
<ion-icon name="folder-open-outline"></ion-icon>
|
|
||||||
<ion-icon name="grid-outline"></ion-icon>
|
|
||||||
<ion-icon name="help-circle-outline"></ion-icon>
|
|
||||||
<ion-icon name="hammer-outline"></ion-icon>
|
|
||||||
<ion-icon name="home-outline"></ion-icon>
|
|
||||||
<ion-icon name="information-circle-outline"></ion-icon>
|
|
||||||
<ion-icon name="key-outline"></ion-icon>
|
|
||||||
<ion-icon name="list-outline"></ion-icon>
|
|
||||||
<ion-icon name="lock-closed-outline"></ion-icon>
|
|
||||||
<ion-icon name="logo-bitcoin"></ion-icon>
|
|
||||||
<ion-icon name="mail-outline"></ion-icon>
|
|
||||||
<ion-icon name="map-outline"></ion-icon>
|
|
||||||
<ion-icon name="medkit-outline"></ion-icon>
|
|
||||||
<ion-icon name="newspaper-outline"></ion-icon>
|
|
||||||
<ion-icon name="notifications-outline"></ion-icon>
|
|
||||||
<ion-icon name="open-outline"></ion-icon>
|
|
||||||
<ion-icon name="options-outline"></ion-icon>
|
|
||||||
<ion-icon name="pencil"></ion-icon>
|
|
||||||
<ion-icon name="phone-portrait-outline"></ion-icon>
|
|
||||||
<ion-icon name="play-circle-outline"></ion-icon>
|
|
||||||
<ion-icon name="power"></ion-icon>
|
|
||||||
<ion-icon name="pulse"></ion-icon>
|
|
||||||
<ion-icon name="qr-code-outline"></ion-icon>
|
|
||||||
<ion-icon name="receipt-outline"></ion-icon>
|
|
||||||
<ion-icon name="refresh"></ion-icon>
|
|
||||||
<ion-icon name="reload"></ion-icon>
|
|
||||||
<ion-icon name="remove"></ion-icon>
|
|
||||||
<ion-icon name="remove-circle-outline"></ion-icon>
|
|
||||||
<ion-icon name="remove-outline"></ion-icon>
|
|
||||||
<ion-icon name="reorder-three"></ion-icon>
|
|
||||||
<ion-icon name="rocket-outline"></ion-icon>
|
|
||||||
<ion-icon name="save-outline"></ion-icon>
|
|
||||||
<ion-icon name="shield-checkmark-outline"></ion-icon>
|
|
||||||
<ion-icon name="stop-outline"></ion-icon>
|
|
||||||
<ion-icon name="storefront-outline"></ion-icon>
|
|
||||||
<ion-icon name="swap-vertical"></ion-icon>
|
|
||||||
<ion-icon name="terminal-outline"></ion-icon>
|
|
||||||
<ion-icon name="trash"></ion-icon>
|
|
||||||
<ion-icon name="trash-outline"></ion-icon>
|
|
||||||
<ion-icon name="warning-outline"></ion-icon>
|
|
||||||
<ion-icon name="wifi"></ion-icon>
|
|
||||||
|
|
||||||
<!-- Ionic components -->
|
|
||||||
<ion-action-sheet></ion-action-sheet>
|
|
||||||
<ion-alert></ion-alert>
|
|
||||||
<ion-back-button></ion-back-button>
|
|
||||||
<ion-badge></ion-badge>
|
|
||||||
<ion-button></ion-button>
|
|
||||||
<ion-buttons></ion-buttons>
|
|
||||||
<ion-card></ion-card>
|
|
||||||
<ion-card-content></ion-card-content>
|
|
||||||
<ion-card-header></ion-card-header>
|
|
||||||
<ion-checkbox></ion-checkbox>
|
|
||||||
<ion-content></ion-content>
|
|
||||||
<ion-footer></ion-footer>
|
|
||||||
<ion-grid></ion-grid>
|
|
||||||
<ion-header></ion-header>
|
|
||||||
<ion-popover></ion-popover>
|
|
||||||
<ion-content>
|
|
||||||
<ion-refresher slot="fixed"></ion-refresher>
|
|
||||||
<ion-refresher-content pullingContent="lines"></ion-refresher-content>
|
|
||||||
<ion-infinite-scroll></ion-infinite-scroll>
|
|
||||||
<ion-infinite-scroll-content
|
|
||||||
loadingSpinner="lines"
|
|
||||||
></ion-infinite-scroll-content>
|
|
||||||
</ion-content>
|
|
||||||
<ion-input></ion-input>
|
|
||||||
<ion-item></ion-item>
|
|
||||||
<ion-item-divider></ion-item-divider>
|
|
||||||
<ion-item-group></ion-item-group>
|
|
||||||
<ion-label></ion-label>
|
|
||||||
<ion-list></ion-list>
|
|
||||||
<ion-loading></ion-loading>
|
|
||||||
<ion-modal></ion-modal>
|
|
||||||
<ion-note></ion-note>
|
|
||||||
<ion-radio></ion-radio>
|
|
||||||
<ion-reorder></ion-reorder>
|
|
||||||
<ion-row></ion-row>
|
|
||||||
<ion-searchbar></ion-searchbar>
|
|
||||||
<ion-segment></ion-segment>
|
|
||||||
<ion-segment-button></ion-segment-button>
|
|
||||||
<ion-select></ion-select>
|
|
||||||
<ion-select-option></ion-select-option>
|
|
||||||
<ion-slides></ion-slides>
|
|
||||||
<ion-spinner name="lines"></ion-spinner>
|
|
||||||
<ion-text></ion-text>
|
|
||||||
<ion-text style="font-weight: bold">load bold</ion-text>
|
|
||||||
<ion-title></ion-title>
|
|
||||||
<ion-toast></ion-toast>
|
|
||||||
<ion-toggle></ion-toggle>
|
|
||||||
<ion-toolbar></ion-toolbar>
|
|
||||||
<ion-menu-button></ion-menu-button>
|
|
||||||
|
|
||||||
<!-- fonts -->
|
|
||||||
<p style="font-family: Montserrat">a</p>
|
|
||||||
<p style="font-family: Montserrat; font-weight: bold">a</p>
|
|
||||||
<p style="font-family: Montserrat; font-weight: 100">a</p>
|
|
||||||
<p style="font-family: Open Sans">a</p>
|
|
||||||
<p style="font-family: Open Sans; font-weight: bold">a</p>
|
|
||||||
<p style="font-family: Open Sans; font-weight: 100">a</p>
|
|
||||||
|
|
||||||
<!-- images -->
|
|
||||||
<img src="assets/img/logo.png" />
|
|
||||||
<img src="assets/img/icons/snek.png" />
|
|
||||||
<img src="assets/img/icons/wifi-1.png" />
|
|
||||||
<img src="assets/img/icons/wifi-2.png" />
|
|
||||||
<img src="assets/img/icons/wifi-3.png" />
|
|
||||||
</section>
|
|
||||||
</ion-content>
|
</ion-content>
|
||||||
<ion-footer
|
<ion-footer>
|
||||||
[ngStyle]="{
|
<footer appFooter></footer>
|
||||||
'max-height': osUpdateProgress ? '100px' : '0px',
|
|
||||||
overflow: 'hidden',
|
|
||||||
'transition-property': 'max-height',
|
|
||||||
'transition-duration': '1s',
|
|
||||||
'transition-delay': '.05s'
|
|
||||||
}"
|
|
||||||
>
|
|
||||||
<ion-toolbar
|
|
||||||
style="border-top: 1px solid var(--ion-color-dark)"
|
|
||||||
color="light"
|
|
||||||
>
|
|
||||||
<ion-list>
|
|
||||||
<ion-list-header>
|
|
||||||
<ion-label
|
|
||||||
>Downloading EOS:
|
|
||||||
{{
|
|
||||||
(
|
|
||||||
(100 * (osUpdateProgress?.downloaded || 1)) /
|
|
||||||
(osUpdateProgress?.size || 1)
|
|
||||||
).toFixed(0)
|
|
||||||
}}%</ion-label
|
|
||||||
>
|
|
||||||
</ion-list-header>
|
|
||||||
<div style="padding: 0 16px 16px 16px">
|
|
||||||
<ion-progress-bar
|
|
||||||
color="secondary"
|
|
||||||
[value]="
|
|
||||||
osUpdateProgress &&
|
|
||||||
osUpdateProgress.downloaded / osUpdateProgress.size
|
|
||||||
"
|
|
||||||
></ion-progress-bar>
|
|
||||||
</div>
|
|
||||||
</ion-list>
|
|
||||||
</ion-toolbar>
|
|
||||||
</ion-footer>
|
</ion-footer>
|
||||||
</ion-app>
|
</ion-app>
|
||||||
|
|||||||
@@ -1,11 +1,7 @@
|
|||||||
.bold {
|
:host {
|
||||||
font-weight: bold;
|
display: block;
|
||||||
}
|
|
||||||
|
|
||||||
.dim {
|
|
||||||
color: var(--ion-color-dark-shade);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ion-split-pane {
|
ion-split-pane {
|
||||||
--side-max-width: 280px;
|
--side-max-width: 280px;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Component, HostListener, Inject, NgZone } from '@angular/core'
|
import { Component, HostListener, NgZone } from '@angular/core'
|
||||||
import { Router, RoutesRecognized } from '@angular/router'
|
import { Router } from '@angular/router'
|
||||||
import {
|
import {
|
||||||
AlertController,
|
AlertController,
|
||||||
IonicSafeString,
|
IonicSafeString,
|
||||||
@@ -31,11 +31,10 @@ import {
|
|||||||
ConnectionService,
|
ConnectionService,
|
||||||
} from './services/connection.service'
|
} from './services/connection.service'
|
||||||
import { ConfigService } from './services/config.service'
|
import { ConfigService } from './services/config.service'
|
||||||
import { ServerStatus, UIData } from 'src/app/services/patch-db/data-model'
|
import { UIData } from 'src/app/services/patch-db/data-model'
|
||||||
import { LocalStorageService } from './services/local-storage.service'
|
import { LocalStorageService } from './services/local-storage.service'
|
||||||
import { EOSService } from './services/eos.service'
|
import { EOSService } from './services/eos.service'
|
||||||
import { OSWelcomePage } from './modals/os-welcome/os-welcome.page'
|
import { OSWelcomePage } from './modals/os-welcome/os-welcome.page'
|
||||||
import { SnakePage } from './modals/snake/snake.page'
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-root',
|
selector: 'app-root',
|
||||||
@@ -43,83 +42,11 @@ import { SnakePage } from './modals/snake/snake.page'
|
|||||||
styleUrls: ['app.component.scss'],
|
styleUrls: ['app.component.scss'],
|
||||||
})
|
})
|
||||||
export class AppComponent {
|
export class AppComponent {
|
||||||
code = {
|
|
||||||
s: false,
|
|
||||||
n: false,
|
|
||||||
e: false,
|
|
||||||
k: false,
|
|
||||||
unlocked: false,
|
|
||||||
}
|
|
||||||
|
|
||||||
@HostListener('document:keydown.enter', ['$event'])
|
|
||||||
@debounce()
|
|
||||||
handleKeyboardEvent() {
|
|
||||||
const elems = document.getElementsByClassName('enter-click')
|
|
||||||
const elem = elems[elems.length - 1] as HTMLButtonElement
|
|
||||||
if (!elem || elem.classList.contains('no-click') || elem.disabled) return
|
|
||||||
if (elem) elem.click()
|
|
||||||
}
|
|
||||||
|
|
||||||
@HostListener('document:keypress', ['$event'])
|
|
||||||
async keyPress(e: KeyboardEvent) {
|
|
||||||
if (e.repeat || this.code.unlocked) return
|
|
||||||
if (this.code[e.key] === false) {
|
|
||||||
this.code[e.key] = true
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
Object.entries(this.code)
|
|
||||||
.filter(([key, value]) => key.length === 1)
|
|
||||||
.map(([key, value]) => value)
|
|
||||||
.reduce((a, b) => a && b)
|
|
||||||
) {
|
|
||||||
await this.openSnek()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@HostListener('document:keyup', ['$event'])
|
|
||||||
keyUp(e: KeyboardEvent) {
|
|
||||||
if (this.code[e.key]) {
|
|
||||||
this.code[e.key] = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ServerStatus = ServerStatus
|
|
||||||
showMenu = false
|
showMenu = false
|
||||||
selectedIndex = 0
|
|
||||||
offlineToast: HTMLIonToastElement
|
offlineToast: HTMLIonToastElement
|
||||||
updateToast: HTMLIonToastElement
|
updateToast: HTMLIonToastElement
|
||||||
notificationToast: HTMLIonToastElement
|
notificationToast: HTMLIonToastElement
|
||||||
serverName: string
|
|
||||||
unreadCount: number
|
|
||||||
subscriptions: Subscription[] = []
|
subscriptions: Subscription[] = []
|
||||||
osUpdateProgress: { size: number; downloaded: number }
|
|
||||||
appPages = [
|
|
||||||
{
|
|
||||||
title: 'Services',
|
|
||||||
url: '/services',
|
|
||||||
icon: 'grid-outline',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Embassy',
|
|
||||||
url: '/embassy',
|
|
||||||
icon: 'cube-outline',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Marketplace',
|
|
||||||
url: '/marketplace',
|
|
||||||
icon: 'storefront-outline',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Notifications',
|
|
||||||
url: '/notifications',
|
|
||||||
icon: 'notifications-outline',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Developer Tools',
|
|
||||||
url: '/developer',
|
|
||||||
icon: 'hammer-outline',
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private readonly storage: Storage,
|
private readonly storage: Storage,
|
||||||
@@ -135,14 +62,29 @@ export class AppComponent {
|
|||||||
private readonly errToast: ErrorToastService,
|
private readonly errToast: ErrorToastService,
|
||||||
private readonly config: ConfigService,
|
private readonly config: ConfigService,
|
||||||
private readonly zone: NgZone,
|
private readonly zone: NgZone,
|
||||||
public readonly splitPane: SplitPaneTracker,
|
private readonly splitPane: SplitPaneTracker,
|
||||||
public readonly patch: PatchDbService,
|
private readonly patch: PatchDbService,
|
||||||
public readonly localStorageService: LocalStorageService,
|
private readonly localStorageService: LocalStorageService,
|
||||||
public readonly eosService: EOSService,
|
private readonly eosService: EOSService,
|
||||||
) {
|
) {
|
||||||
this.init()
|
this.init()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@HostListener('document:keydown.enter', ['$event'])
|
||||||
|
@debounce()
|
||||||
|
handleKeyboardEvent() {
|
||||||
|
const elems = document.getElementsByClassName('enter-click')
|
||||||
|
const elem = elems[elems.length - 1] as HTMLButtonElement
|
||||||
|
|
||||||
|
if (elem && !elem.classList.contains('no-click') && !elem.disabled) {
|
||||||
|
elem.click()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
splitPaneVisible({ detail }: any) {
|
||||||
|
this.splitPane.sidebarOpen$.next(detail.visible)
|
||||||
|
}
|
||||||
|
|
||||||
async init() {
|
async init() {
|
||||||
await this.storage.create()
|
await this.storage.create()
|
||||||
await this.authService.init()
|
await this.authService.init()
|
||||||
@@ -167,8 +109,6 @@ export class AppComponent {
|
|||||||
...this.connectionService.start(),
|
...this.connectionService.start(),
|
||||||
// watch connection to display connectivity issues
|
// watch connection to display connectivity issues
|
||||||
this.watchConnection(),
|
this.watchConnection(),
|
||||||
// watch router to highlight selected menu item
|
|
||||||
this.watchRouter(),
|
|
||||||
])
|
])
|
||||||
|
|
||||||
this.patch
|
this.patch
|
||||||
@@ -186,8 +126,6 @@ export class AppComponent {
|
|||||||
this.subscriptions = this.subscriptions.concat([
|
this.subscriptions = this.subscriptions.concat([
|
||||||
// watch status to present toast for updated state
|
// watch status to present toast for updated state
|
||||||
this.watchStatus(),
|
this.watchStatus(),
|
||||||
// watch update-progress to present progress bar when server is updating
|
|
||||||
this.watchUpdateProgress(),
|
|
||||||
// watch version to refresh browser window
|
// watch version to refresh browser window
|
||||||
this.watchVersion(),
|
this.watchVersion(),
|
||||||
// watch unread notification count to display toast
|
// watch unread notification count to display toast
|
||||||
@@ -212,40 +150,6 @@ export class AppComponent {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async goToWebsite(): Promise<void> {
|
|
||||||
let url: string
|
|
||||||
if (this.config.isTor()) {
|
|
||||||
url =
|
|
||||||
'http://privacy34kn4ez3y3nijweec6w4g54i3g54sdv7r5mr6soma3w4begyd.onion'
|
|
||||||
} else {
|
|
||||||
url = 'https://start9.com'
|
|
||||||
}
|
|
||||||
window.open(url, '_blank', 'noreferrer')
|
|
||||||
}
|
|
||||||
|
|
||||||
async presentAlertLogout() {
|
|
||||||
const alert = await this.alertCtrl.create({
|
|
||||||
header: 'Caution',
|
|
||||||
message:
|
|
||||||
'Do you know your password? If you log out and forget your password, you may permanently lose access to your Embassy.',
|
|
||||||
buttons: [
|
|
||||||
{
|
|
||||||
text: 'Cancel',
|
|
||||||
role: 'cancel',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: 'Logout',
|
|
||||||
cssClass: 'enter-click',
|
|
||||||
handler: () => {
|
|
||||||
this.logout()
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
})
|
|
||||||
|
|
||||||
await alert.present()
|
|
||||||
}
|
|
||||||
|
|
||||||
private checkForEosUpdate(ui: UIData): void {
|
private checkForEosUpdate(ui: UIData): void {
|
||||||
if (ui['auto-check-updates'] !== false) {
|
if (ui['auto-check-updates'] !== false) {
|
||||||
this.eosService.getEOS()
|
this.eosService.getEOS()
|
||||||
@@ -271,48 +175,6 @@ export class AppComponent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async openSnek() {
|
|
||||||
this.code.unlocked = true
|
|
||||||
const modal = await this.modalCtrl.create({
|
|
||||||
component: SnakePage,
|
|
||||||
cssClass: 'snake-modal',
|
|
||||||
backdropDismiss: false,
|
|
||||||
})
|
|
||||||
|
|
||||||
modal.onDidDismiss().then(async ret => {
|
|
||||||
this.code.unlocked = false
|
|
||||||
if (
|
|
||||||
ret.data.highScore &&
|
|
||||||
(ret.data.highScore >
|
|
||||||
this.patch.getData().ui.gaming?.snake?.['high-score'] ||
|
|
||||||
!this.patch.getData().ui.gaming?.snake?.['high-score'])
|
|
||||||
) {
|
|
||||||
const loader = await this.loadingCtrl.create({
|
|
||||||
spinner: 'lines',
|
|
||||||
cssClass: 'loader',
|
|
||||||
message: 'Saving High Score...',
|
|
||||||
})
|
|
||||||
await loader.present()
|
|
||||||
try {
|
|
||||||
await this.embassyApi.setDbValue({
|
|
||||||
pointer: '/gaming',
|
|
||||||
value: { snake: { 'high-score': ret.data.highScore } },
|
|
||||||
})
|
|
||||||
} catch (e) {
|
|
||||||
this.errToast.present(e)
|
|
||||||
} finally {
|
|
||||||
this.loadingCtrl.dismiss()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
modal.present()
|
|
||||||
}
|
|
||||||
// should wipe cache independant of actual BE logout
|
|
||||||
private async logout() {
|
|
||||||
this.embassyApi.logout({})
|
|
||||||
this.authService.setUnverified()
|
|
||||||
}
|
|
||||||
|
|
||||||
private watchConnection(): Subscription {
|
private watchConnection(): Subscription {
|
||||||
return this.connectionService
|
return this.connectionService
|
||||||
.watchFailure$()
|
.watchFailure$()
|
||||||
@@ -344,17 +206,6 @@ export class AppComponent {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
private watchRouter(): Subscription {
|
|
||||||
return this.router.events
|
|
||||||
.pipe(filter((e: RoutesRecognized) => !!e.urlAfterRedirects))
|
|
||||||
.subscribe(e => {
|
|
||||||
const appPageIndex = this.appPages.findIndex(appPage =>
|
|
||||||
e.urlAfterRedirects.startsWith(appPage.url),
|
|
||||||
)
|
|
||||||
if (appPageIndex > -1) this.selectedIndex = appPageIndex
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
private watchStatus(): Subscription {
|
private watchStatus(): Subscription {
|
||||||
return this.patch
|
return this.patch
|
||||||
.watch$('server-info', 'status-info', 'updated')
|
.watch$('server-info', 'status-info', 'updated')
|
||||||
@@ -364,15 +215,6 @@ export class AppComponent {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
m
|
|
||||||
|
|
||||||
private watchUpdateProgress(): Subscription {
|
|
||||||
return this.patch
|
|
||||||
.watch$('server-info', 'status-info', 'update-progress')
|
|
||||||
.subscribe(progress => {
|
|
||||||
this.osUpdateProgress = progress
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
private watchVersion(): Subscription {
|
private watchVersion(): Subscription {
|
||||||
return this.patch.watch$('server-info', 'version').subscribe(version => {
|
return this.patch.watch$('server-info', 'version').subscribe(version => {
|
||||||
@@ -387,7 +229,6 @@ export class AppComponent {
|
|||||||
return this.patch
|
return this.patch
|
||||||
.watch$('server-info', 'unread-notification-count')
|
.watch$('server-info', 'unread-notification-count')
|
||||||
.subscribe(count => {
|
.subscribe(count => {
|
||||||
this.unreadCount = count
|
|
||||||
if (previous !== undefined && count > previous)
|
if (previous !== undefined && count > previous)
|
||||||
this.presentToastNotifications()
|
this.presentToastNotifications()
|
||||||
previous = count
|
previous = count
|
||||||
@@ -530,8 +371,4 @@ export class AppComponent {
|
|||||||
loader.dismiss()
|
loader.dismiss()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
splitPaneVisible(e: any) {
|
|
||||||
this.splitPane.sidebarOpen$.next(e.detail.visible)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { NgModule, CUSTOM_ELEMENTS_SCHEMA, ErrorHandler } from '@angular/core'
|
import { NgModule, ErrorHandler } from '@angular/core'
|
||||||
import { BrowserModule } from '@angular/platform-browser'
|
import { BrowserAnimationsModule } from '@angular/platform-browser/animations'
|
||||||
import { RouteReuseStrategy } from '@angular/router'
|
import { RouteReuseStrategy } from '@angular/router'
|
||||||
import { IonicModule, IonicRouteStrategy, IonNav } from '@ionic/angular'
|
import { IonicModule, IonicRouteStrategy, IonNav } from '@ionic/angular'
|
||||||
import { Drivers } from '@ionic/storage'
|
import { Drivers } from '@ionic/storage'
|
||||||
@@ -10,7 +10,6 @@ import { AppRoutingModule } from './app-routing.module'
|
|||||||
import { ApiService } from './services/api/embassy-api.service'
|
import { ApiService } from './services/api/embassy-api.service'
|
||||||
import { PatchDbServiceFactory } from './services/patch-db/patch-db.factory'
|
import { PatchDbServiceFactory } from './services/patch-db/patch-db.factory'
|
||||||
import { ConfigService } from './services/config.service'
|
import { ConfigService } from './services/config.service'
|
||||||
import { QrCodeModule } from 'ng-qrcode'
|
|
||||||
import { OSWelcomePageModule } from './modals/os-welcome/os-welcome.module'
|
import { OSWelcomePageModule } from './modals/os-welcome/os-welcome.module'
|
||||||
import { PatchDbService } from './services/patch-db/patch-db.service'
|
import { PatchDbService } from './services/patch-db/patch-db.service'
|
||||||
import { LocalStorageBootstrap } from './services/patch-db/local-storage-bootstrap'
|
import { LocalStorageBootstrap } from './services/patch-db/local-storage-bootstrap'
|
||||||
@@ -27,6 +26,9 @@ import {
|
|||||||
WorkspaceConfig,
|
WorkspaceConfig,
|
||||||
} from '@start9labs/shared'
|
} from '@start9labs/shared'
|
||||||
import { MarketplaceModule } from './marketplace.module'
|
import { MarketplaceModule } from './marketplace.module'
|
||||||
|
import { PreloaderModule } from './app/preloader/preloader.module'
|
||||||
|
import { FooterModule } from './app/footer/footer.module'
|
||||||
|
import { MenuModule } from './app/menu/menu.module'
|
||||||
|
|
||||||
const { useMocks } = require('../../../../config.json') as WorkspaceConfig
|
const { useMocks } = require('../../../../config.json') as WorkspaceConfig
|
||||||
|
|
||||||
@@ -35,7 +37,7 @@ const { useMocks } = require('../../../../config.json') as WorkspaceConfig
|
|||||||
entryComponents: [],
|
entryComponents: [],
|
||||||
imports: [
|
imports: [
|
||||||
HttpClientModule,
|
HttpClientModule,
|
||||||
BrowserModule,
|
BrowserAnimationsModule,
|
||||||
IonicModule.forRoot({
|
IonicModule.forRoot({
|
||||||
mode: 'md',
|
mode: 'md',
|
||||||
}),
|
}),
|
||||||
@@ -46,7 +48,9 @@ const { useMocks } = require('../../../../config.json') as WorkspaceConfig
|
|||||||
name: '_embassystorage',
|
name: '_embassystorage',
|
||||||
driverOrder: [Drivers.LocalStorage, Drivers.IndexedDB],
|
driverOrder: [Drivers.LocalStorage, Drivers.IndexedDB],
|
||||||
}),
|
}),
|
||||||
QrCodeModule,
|
MenuModule,
|
||||||
|
PreloaderModule,
|
||||||
|
FooterModule,
|
||||||
OSWelcomePageModule,
|
OSWelcomePageModule,
|
||||||
MarkdownModule,
|
MarkdownModule,
|
||||||
GenericInputComponentModule,
|
GenericInputComponentModule,
|
||||||
@@ -82,6 +86,5 @@ const { useMocks } = require('../../../../config.json') as WorkspaceConfig
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
bootstrap: [AppComponent],
|
bootstrap: [AppComponent],
|
||||||
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
|
||||||
})
|
})
|
||||||
export class AppModule {}
|
export class AppModule {}
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
<ion-toolbar
|
||||||
|
*ngIf="progress$ | async as progress"
|
||||||
|
color="light"
|
||||||
|
[@heightCollapse]="animation"
|
||||||
|
>
|
||||||
|
<ion-list class="list">
|
||||||
|
<ion-list-header>
|
||||||
|
<ion-label>Downloading EOS: {{ getProgress(progress) }}%</ion-label>
|
||||||
|
</ion-list-header>
|
||||||
|
<ion-progress-bar
|
||||||
|
class="progress"
|
||||||
|
color="secondary"
|
||||||
|
[value]="getProgress(progress) / 100"
|
||||||
|
></ion-progress-bar>
|
||||||
|
</ion-list>
|
||||||
|
</ion-toolbar>
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
.list {
|
||||||
|
box-shadow: inset 0 1px var(--ion-color-dark);
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress {
|
||||||
|
width: auto;
|
||||||
|
margin: 0 16px 16px 16px;
|
||||||
|
}
|
||||||
36
frontend/projects/ui/src/app/app/footer/footer.component.ts
Normal file
36
frontend/projects/ui/src/app/app/footer/footer.component.ts
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
import { ChangeDetectionStrategy, Component, Input } from '@angular/core'
|
||||||
|
|
||||||
|
import { heightCollapse } from '../../util/animations'
|
||||||
|
import { PatchDbService } from '../../services/patch-db/patch-db.service'
|
||||||
|
import { map } from 'rxjs/operators'
|
||||||
|
import { ServerInfo } from '../../services/patch-db/data-model'
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'footer[appFooter]',
|
||||||
|
templateUrl: 'footer.component.html',
|
||||||
|
styleUrls: ['footer.component.scss'],
|
||||||
|
animations: [heightCollapse],
|
||||||
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
|
})
|
||||||
|
export class FooterComponent {
|
||||||
|
readonly progress$ = this.patch
|
||||||
|
.watch$('server-info', 'status-info', 'update-progress')
|
||||||
|
.pipe(map(a => a && { ...a }))
|
||||||
|
|
||||||
|
readonly animation = {
|
||||||
|
value: '',
|
||||||
|
params: {
|
||||||
|
duration: 1000,
|
||||||
|
delay: 50,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(private readonly patch: PatchDbService) {}
|
||||||
|
|
||||||
|
getProgress({
|
||||||
|
downloaded,
|
||||||
|
size,
|
||||||
|
}: ServerInfo['status-info']['update-progress']): number {
|
||||||
|
return Math.round((100 * (downloaded || 1)) / (size || 1))
|
||||||
|
}
|
||||||
|
}
|
||||||
12
frontend/projects/ui/src/app/app/footer/footer.module.ts
Normal file
12
frontend/projects/ui/src/app/app/footer/footer.module.ts
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import { CommonModule } from '@angular/common'
|
||||||
|
import { NgModule } from '@angular/core'
|
||||||
|
import { IonicModule } from '@ionic/angular'
|
||||||
|
|
||||||
|
import { FooterComponent } from './footer.component'
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
imports: [CommonModule, IonicModule],
|
||||||
|
declarations: [FooterComponent],
|
||||||
|
exports: [FooterComponent],
|
||||||
|
})
|
||||||
|
export class FooterModule {}
|
||||||
60
frontend/projects/ui/src/app/app/menu/menu.component.html
Normal file
60
frontend/projects/ui/src/app/app/menu/menu.component.html
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
<a class="logo ion-padding" target="_blank" rel="noreferrer" [href]="href">
|
||||||
|
<img alt="Start9" src="assets/img/logo.png" />
|
||||||
|
</a>
|
||||||
|
<div class="divider"></div>
|
||||||
|
<ion-item-group class="menu">
|
||||||
|
<ion-menu-toggle *ngFor="let page of pages; let i = index" auto-hide="false">
|
||||||
|
<ion-item
|
||||||
|
*ngIf="
|
||||||
|
page.url !== '/developer' || (localStorageService.showDevTools$ | async)
|
||||||
|
"
|
||||||
|
button
|
||||||
|
class="link"
|
||||||
|
color="transparent"
|
||||||
|
routerDirection="root"
|
||||||
|
lines="none"
|
||||||
|
detail="false"
|
||||||
|
[routerLink]="page.url"
|
||||||
|
>
|
||||||
|
<ion-icon
|
||||||
|
slot="start"
|
||||||
|
class="icon label"
|
||||||
|
routerLinkActive="label_selected"
|
||||||
|
[name]="page.icon"
|
||||||
|
></ion-icon>
|
||||||
|
<ion-label class="label montserrat" routerLinkActive="label_selected">
|
||||||
|
{{ page.title }}
|
||||||
|
</ion-label>
|
||||||
|
<ion-icon
|
||||||
|
*ngIf="page.url === '/embassy' && eosService.updateAvailable$ | async"
|
||||||
|
color="success"
|
||||||
|
size="small"
|
||||||
|
name="rocket-outline"
|
||||||
|
></ion-icon>
|
||||||
|
<ion-badge
|
||||||
|
*ngIf="page.url === '/notifications' && notification$ | async as count"
|
||||||
|
color="danger"
|
||||||
|
class="badge"
|
||||||
|
>
|
||||||
|
{{ count }}
|
||||||
|
</ion-badge>
|
||||||
|
</ion-item>
|
||||||
|
</ion-menu-toggle>
|
||||||
|
</ion-item-group>
|
||||||
|
<img appSnek class="snek" alt="Play Snek" src="assets/img/icons/snek.png" />
|
||||||
|
<div class="bottom">
|
||||||
|
<div class="divider" style="margin-bottom: 10px"></div>
|
||||||
|
<ion-menu-toggle auto-hide="false">
|
||||||
|
<ion-item
|
||||||
|
button
|
||||||
|
lines="none"
|
||||||
|
style="--background: transparent; margin-bottom: 86px; text-align: center"
|
||||||
|
fill="clear"
|
||||||
|
(click)="presentAlertLogout()"
|
||||||
|
>
|
||||||
|
<ion-label>
|
||||||
|
<ion-text class="montserrat" color="dark"> Log Out </ion-text>
|
||||||
|
</ion-label>
|
||||||
|
</ion-item>
|
||||||
|
</ion-menu-toggle>
|
||||||
|
</div>
|
||||||
47
frontend/projects/ui/src/app/app/menu/menu.component.scss
Normal file
47
frontend/projects/ui/src/app/app/menu/menu.component.scss
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
:host {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo {
|
||||||
|
display: block;
|
||||||
|
width: 50%;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu {
|
||||||
|
padding: 30px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.label {
|
||||||
|
color: var(--ion-color-dark-shade);
|
||||||
|
|
||||||
|
&_selected {
|
||||||
|
color: #fff;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge {
|
||||||
|
margin-right: 3%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.snek {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 90px;
|
||||||
|
left: 20px;
|
||||||
|
width: 20px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bottom {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
right: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 75px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
93
frontend/projects/ui/src/app/app/menu/menu.component.ts
Normal file
93
frontend/projects/ui/src/app/app/menu/menu.component.ts
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
import { ChangeDetectionStrategy, Component } from '@angular/core'
|
||||||
|
import { AlertController } from '@ionic/angular'
|
||||||
|
|
||||||
|
import { ConfigService } from '../../services/config.service'
|
||||||
|
import { LocalStorageService } from '../../services/local-storage.service'
|
||||||
|
import { EOSService } from '../../services/eos.service'
|
||||||
|
import { ApiService } from '../../services/api/embassy-api.service'
|
||||||
|
import { AuthService } from '../../services/auth.service'
|
||||||
|
import { PatchDbService } from '../../services/patch-db/patch-db.service'
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-menu',
|
||||||
|
templateUrl: 'menu.component.html',
|
||||||
|
styleUrls: ['menu.component.scss'],
|
||||||
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
|
})
|
||||||
|
export class MenuComponent {
|
||||||
|
readonly pages = [
|
||||||
|
{
|
||||||
|
title: 'Services',
|
||||||
|
url: '/services',
|
||||||
|
icon: 'grid-outline',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Embassy',
|
||||||
|
url: '/embassy',
|
||||||
|
icon: 'cube-outline',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Marketplace',
|
||||||
|
url: '/marketplace',
|
||||||
|
icon: 'storefront-outline',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Notifications',
|
||||||
|
url: '/notifications',
|
||||||
|
icon: 'notifications-outline',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Developer Tools',
|
||||||
|
url: '/developer',
|
||||||
|
icon: 'hammer-outline',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
readonly notification$ = this.patch.watch$(
|
||||||
|
'server-info',
|
||||||
|
'unread-notification-count',
|
||||||
|
)
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private readonly config: ConfigService,
|
||||||
|
private readonly alertCtrl: AlertController,
|
||||||
|
private readonly embassyApi: ApiService,
|
||||||
|
private readonly authService: AuthService,
|
||||||
|
private readonly patch: PatchDbService,
|
||||||
|
public readonly localStorageService: LocalStorageService,
|
||||||
|
public readonly eosService: EOSService,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
get href(): string {
|
||||||
|
return this.config.isTor()
|
||||||
|
? 'http://privacy34kn4ez3y3nijweec6w4g54i3g54sdv7r5mr6soma3w4begyd.onion'
|
||||||
|
: 'https://start9.com'
|
||||||
|
}
|
||||||
|
|
||||||
|
async presentAlertLogout() {
|
||||||
|
const alert = await this.alertCtrl.create({
|
||||||
|
header: 'Caution',
|
||||||
|
message:
|
||||||
|
'Do you know your password? If you log out and forget your password, you may permanently lose access to your Embassy.',
|
||||||
|
buttons: [
|
||||||
|
{
|
||||||
|
text: 'Cancel',
|
||||||
|
role: 'cancel',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Logout',
|
||||||
|
cssClass: 'enter-click',
|
||||||
|
handler: () => this.logout(),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
|
||||||
|
await alert.present()
|
||||||
|
}
|
||||||
|
|
||||||
|
// should wipe cache independent of actual BE logout
|
||||||
|
private logout() {
|
||||||
|
this.embassyApi.logout({})
|
||||||
|
this.authService.setUnverified()
|
||||||
|
}
|
||||||
|
}
|
||||||
14
frontend/projects/ui/src/app/app/menu/menu.module.ts
Normal file
14
frontend/projects/ui/src/app/app/menu/menu.module.ts
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import { CommonModule } from '@angular/common'
|
||||||
|
import { NgModule } from '@angular/core'
|
||||||
|
import { RouterModule } from '@angular/router'
|
||||||
|
import { IonicModule } from '@ionic/angular'
|
||||||
|
|
||||||
|
import { MenuComponent } from './menu.component'
|
||||||
|
import { SnekModule } from '../snek/snek.module'
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
imports: [CommonModule, IonicModule, RouterModule, SnekModule],
|
||||||
|
declarations: [MenuComponent],
|
||||||
|
exports: [MenuComponent],
|
||||||
|
})
|
||||||
|
export class MenuModule {}
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
<!-- Ionicons -->
|
||||||
|
<ion-icon *ngFor="let icon of icons" [name]="icon"></ion-icon>
|
||||||
|
|
||||||
|
<!-- 3rd party components -->
|
||||||
|
<qr-code value="hello"></qr-code>
|
||||||
|
|
||||||
|
<!-- Ionic components -->
|
||||||
|
<ion-action-sheet></ion-action-sheet>
|
||||||
|
<ion-alert></ion-alert>
|
||||||
|
<ion-back-button></ion-back-button>
|
||||||
|
<ion-badge></ion-badge>
|
||||||
|
<ion-button></ion-button>
|
||||||
|
<ion-buttons></ion-buttons>
|
||||||
|
<ion-card></ion-card>
|
||||||
|
<ion-card-content></ion-card-content>
|
||||||
|
<ion-card-header></ion-card-header>
|
||||||
|
<ion-checkbox></ion-checkbox>
|
||||||
|
<ion-content></ion-content>
|
||||||
|
<ion-footer></ion-footer>
|
||||||
|
<ion-grid></ion-grid>
|
||||||
|
<ion-header></ion-header>
|
||||||
|
<ion-popover></ion-popover>
|
||||||
|
<ion-content>
|
||||||
|
<ion-refresher slot="fixed"></ion-refresher>
|
||||||
|
<ion-refresher-content pullingContent="lines"></ion-refresher-content>
|
||||||
|
<ion-infinite-scroll></ion-infinite-scroll>
|
||||||
|
<ion-infinite-scroll-content
|
||||||
|
loadingSpinner="lines"
|
||||||
|
></ion-infinite-scroll-content>
|
||||||
|
</ion-content>
|
||||||
|
<ion-input></ion-input>
|
||||||
|
<ion-item></ion-item>
|
||||||
|
<ion-item-divider></ion-item-divider>
|
||||||
|
<ion-item-group></ion-item-group>
|
||||||
|
<ion-label></ion-label>
|
||||||
|
<ion-list></ion-list>
|
||||||
|
<ion-loading></ion-loading>
|
||||||
|
<ion-modal></ion-modal>
|
||||||
|
<ion-note></ion-note>
|
||||||
|
<ion-radio></ion-radio>
|
||||||
|
<ion-reorder></ion-reorder>
|
||||||
|
<ion-row></ion-row>
|
||||||
|
<ion-searchbar></ion-searchbar>
|
||||||
|
<ion-segment></ion-segment>
|
||||||
|
<ion-segment-button></ion-segment-button>
|
||||||
|
<ion-select></ion-select>
|
||||||
|
<ion-select-option></ion-select-option>
|
||||||
|
<ion-slides></ion-slides>
|
||||||
|
<ion-spinner name="lines"></ion-spinner>
|
||||||
|
<ion-text></ion-text>
|
||||||
|
<ion-text><strong>load bold font</strong></ion-text>
|
||||||
|
<ion-title></ion-title>
|
||||||
|
<ion-toast></ion-toast>
|
||||||
|
<ion-toggle></ion-toggle>
|
||||||
|
<ion-toolbar></ion-toolbar>
|
||||||
|
<ion-menu-button></ion-menu-button>
|
||||||
|
|
||||||
|
<!-- fonts -->
|
||||||
|
<p style="font-family: Montserrat">a</p>
|
||||||
|
<p style="font-family: Montserrat; font-weight: bold">a</p>
|
||||||
|
<p style="font-family: Montserrat; font-weight: 100">a</p>
|
||||||
|
<p style="font-family: Open Sans">a</p>
|
||||||
|
<p style="font-family: Open Sans; font-weight: bold">a</p>
|
||||||
|
<p style="font-family: Open Sans; font-weight: 100">a</p>
|
||||||
|
|
||||||
|
<!-- images -->
|
||||||
|
<img src="assets/img/logo.png" />
|
||||||
|
<img src="assets/img/icons/snek.png" />
|
||||||
|
<img src="assets/img/icons/wifi-1.png" />
|
||||||
|
<img src="assets/img/icons/wifi-2.png" />
|
||||||
|
<img src="assets/img/icons/wifi-3.png" />
|
||||||
@@ -0,0 +1,91 @@
|
|||||||
|
import { ChangeDetectionStrategy, Component } from '@angular/core'
|
||||||
|
|
||||||
|
// TODO: Turn into DI token if this is needed someplace else too
|
||||||
|
const ICONS = [
|
||||||
|
'add',
|
||||||
|
'alert-outline',
|
||||||
|
'alert-circle-outline',
|
||||||
|
'aperture-outline',
|
||||||
|
'arrow-back',
|
||||||
|
'arrow-up',
|
||||||
|
'briefcase-outline',
|
||||||
|
'bookmark-outline',
|
||||||
|
'cellular-outline',
|
||||||
|
'chatbubbles-outline',
|
||||||
|
'checkmark',
|
||||||
|
'chevron-down',
|
||||||
|
'chevron-up',
|
||||||
|
'chevron-forward',
|
||||||
|
'close',
|
||||||
|
'cloud-outline',
|
||||||
|
'cloud-done-outline',
|
||||||
|
'cloud-download-outline',
|
||||||
|
'cloud-offline-outline',
|
||||||
|
'cloud-upload-outline',
|
||||||
|
'code-outline',
|
||||||
|
'cog-outline',
|
||||||
|
'color-wand-outline',
|
||||||
|
'construct-outline',
|
||||||
|
'copy-outline',
|
||||||
|
'cube-outline',
|
||||||
|
'desktop-outline',
|
||||||
|
'download-outline',
|
||||||
|
'earth-outline',
|
||||||
|
'ellipsis-horizontal-outline',
|
||||||
|
'eye-off-outline',
|
||||||
|
'eye-outline',
|
||||||
|
'file-tray-stacked-outline',
|
||||||
|
'finger-print-outline',
|
||||||
|
'flash-outline',
|
||||||
|
'folder-open-outline',
|
||||||
|
'grid-outline',
|
||||||
|
'help-circle-outline',
|
||||||
|
'hammer-outline',
|
||||||
|
'home-outline',
|
||||||
|
'information-circle-outline',
|
||||||
|
'key-outline',
|
||||||
|
'list-outline',
|
||||||
|
'lock-closed-outline',
|
||||||
|
'logo-bitcoin',
|
||||||
|
'mail-outline',
|
||||||
|
'map-outline',
|
||||||
|
'medkit-outline',
|
||||||
|
'newspaper-outline',
|
||||||
|
'notifications-outline',
|
||||||
|
'open-outline',
|
||||||
|
'options-outline',
|
||||||
|
'pencil',
|
||||||
|
'phone-portrait-outline',
|
||||||
|
'play-circle-outline',
|
||||||
|
'power',
|
||||||
|
'pulse',
|
||||||
|
'qr-code-outline',
|
||||||
|
'receipt-outline',
|
||||||
|
'refresh',
|
||||||
|
'reload',
|
||||||
|
'remove',
|
||||||
|
'remove-circle-outline',
|
||||||
|
'remove-outline',
|
||||||
|
'reorder-three',
|
||||||
|
'rocket-outline',
|
||||||
|
'save-outline',
|
||||||
|
'shield-checkmark-outline',
|
||||||
|
'stop-outline',
|
||||||
|
'storefront-outline',
|
||||||
|
'swap-vertical',
|
||||||
|
'terminal-outline',
|
||||||
|
'trash',
|
||||||
|
'trash-outline',
|
||||||
|
'warning-outline',
|
||||||
|
'wifi',
|
||||||
|
]
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'section[appPreloader]',
|
||||||
|
templateUrl: 'preloader.component.html',
|
||||||
|
styles: [':host { display: none }'],
|
||||||
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
|
})
|
||||||
|
export class PreloaderComponent {
|
||||||
|
readonly icons = ICONS
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
import { CommonModule } from '@angular/common'
|
||||||
|
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core'
|
||||||
|
import { IonicModule } from '@ionic/angular'
|
||||||
|
import { QrCodeModule } from 'ng-qrcode'
|
||||||
|
|
||||||
|
import { PreloaderComponent } from './preloader.component'
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
imports: [CommonModule, IonicModule, QrCodeModule],
|
||||||
|
declarations: [PreloaderComponent],
|
||||||
|
exports: [PreloaderComponent],
|
||||||
|
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
||||||
|
})
|
||||||
|
export class PreloaderModule {}
|
||||||
88
frontend/projects/ui/src/app/app/snek/snek.directive.ts
Normal file
88
frontend/projects/ui/src/app/app/snek/snek.directive.ts
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
import { Directive, HostListener } from '@angular/core'
|
||||||
|
import { LoadingController, ModalController } from '@ionic/angular'
|
||||||
|
import { ErrorToastService } from '@start9labs/shared'
|
||||||
|
|
||||||
|
import { SnakePage } from '../../modals/snake/snake.page'
|
||||||
|
import { PatchDbService } from '../../services/patch-db/patch-db.service'
|
||||||
|
import { ApiService } from '../../services/api/embassy-api.service'
|
||||||
|
|
||||||
|
const SNEK = ['s', 'n', 'e', 'k']
|
||||||
|
|
||||||
|
@Directive({
|
||||||
|
selector: 'img[appSnek]',
|
||||||
|
})
|
||||||
|
export class SnekDirective {
|
||||||
|
private readonly code = new Map<string, boolean>([
|
||||||
|
...SNEK.map<[string, boolean]>(char => [char, false]),
|
||||||
|
['unlocked', false],
|
||||||
|
])
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private readonly modalCtrl: ModalController,
|
||||||
|
private readonly loadingCtrl: LoadingController,
|
||||||
|
private readonly errToast: ErrorToastService,
|
||||||
|
private readonly embassyApi: ApiService,
|
||||||
|
private readonly patch: PatchDbService,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
@HostListener('document:keyup', ['$event.key'])
|
||||||
|
onKeyUp(key: string) {
|
||||||
|
this.code.set(key, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
@HostListener('document:keypress', ['$event'])
|
||||||
|
async onKeyPress({ repeat, key }: KeyboardEvent) {
|
||||||
|
if (repeat || this.code.get('unlocked')) return
|
||||||
|
|
||||||
|
this.code.set(key, true)
|
||||||
|
|
||||||
|
if (SNEK.every(char => this.code.get(char))) {
|
||||||
|
await this.openSnek()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@HostListener('click')
|
||||||
|
async onClick() {
|
||||||
|
await this.openSnek()
|
||||||
|
}
|
||||||
|
|
||||||
|
private async openSnek() {
|
||||||
|
this.code.set('unlocked', true)
|
||||||
|
|
||||||
|
const modal = await this.modalCtrl.create({
|
||||||
|
component: SnakePage,
|
||||||
|
cssClass: 'snake-modal',
|
||||||
|
backdropDismiss: false,
|
||||||
|
})
|
||||||
|
|
||||||
|
modal.onDidDismiss().then(async ({ data }) => {
|
||||||
|
this.code.set('unlocked', false)
|
||||||
|
|
||||||
|
const highScore =
|
||||||
|
this.patch.getData().ui.gaming?.snake?.['high-score'] || 0
|
||||||
|
|
||||||
|
if (data?.highScore > highScore) {
|
||||||
|
const loader = await this.loadingCtrl.create({
|
||||||
|
spinner: 'lines',
|
||||||
|
cssClass: 'loader',
|
||||||
|
message: 'Saving High Score...',
|
||||||
|
})
|
||||||
|
|
||||||
|
await loader.present()
|
||||||
|
|
||||||
|
try {
|
||||||
|
await this.embassyApi.setDbValue({
|
||||||
|
pointer: '/gaming',
|
||||||
|
value: { snake: { 'high-score': data.highScore } },
|
||||||
|
})
|
||||||
|
} catch (e) {
|
||||||
|
this.errToast.present(e)
|
||||||
|
} finally {
|
||||||
|
this.loadingCtrl.dismiss()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
modal.present()
|
||||||
|
}
|
||||||
|
}
|
||||||
11
frontend/projects/ui/src/app/app/snek/snek.module.ts
Normal file
11
frontend/projects/ui/src/app/app/snek/snek.module.ts
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import { NgModule } from '@angular/core'
|
||||||
|
|
||||||
|
import { SnakePageModule } from '../../modals/snake/snake.module'
|
||||||
|
import { SnekDirective } from './snek.directive'
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
imports: [SnakePageModule],
|
||||||
|
declarations: [SnekDirective],
|
||||||
|
exports: [SnekDirective],
|
||||||
|
})
|
||||||
|
export class SnekModule {}
|
||||||
@@ -1,11 +1,12 @@
|
|||||||
import { NgModule } from '@angular/core'
|
import { NgModule } from '@angular/core'
|
||||||
import { CommonModule } from '@angular/common'
|
import { CommonModule } from '@angular/common'
|
||||||
import { IonicModule } from '@ionic/angular'
|
import { IonicModule } from '@ionic/angular'
|
||||||
|
|
||||||
import { SnakePage } from './snake.page'
|
import { SnakePage } from './snake.page'
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
declarations: [SnakePage],
|
|
||||||
imports: [CommonModule, IonicModule],
|
imports: [CommonModule, IonicModule],
|
||||||
|
declarations: [SnakePage],
|
||||||
exports: [SnakePage],
|
exports: [SnakePage],
|
||||||
})
|
})
|
||||||
export class SnakePageModule {}
|
export class SnakePageModule {}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
<img [src]="dep.icon" alt="" />
|
<img [src]="dep.icon" alt="" />
|
||||||
</ion-thumbnail>
|
</ion-thumbnail>
|
||||||
<ion-label>
|
<ion-label>
|
||||||
<h2 class="inline">
|
<h2 class="montserrat">
|
||||||
<ion-icon
|
<ion-icon
|
||||||
*ngIf="!!dep.errorText"
|
*ngIf="!!dep.errorText"
|
||||||
class="icon"
|
class="icon"
|
||||||
|
|||||||
@@ -1,7 +1,3 @@
|
|||||||
.inline {
|
|
||||||
font-family: 'Montserrat', sans-serif;
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon {
|
.icon {
|
||||||
padding-right: 4px;
|
padding-right: 4px;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,10 @@
|
|||||||
<img [src]="pkg['static-files'].icon" alt="" />
|
<img [src]="pkg['static-files'].icon" alt="" />
|
||||||
</ion-avatar>
|
</ion-avatar>
|
||||||
<ion-label>
|
<ion-label>
|
||||||
<h1 class="name" [class.less-large]="pkg.manifest.title.length > 20">
|
<h1
|
||||||
|
class="montserrat"
|
||||||
|
[class.less-large]="pkg.manifest.title.length > 20"
|
||||||
|
>
|
||||||
{{ pkg.manifest.title }}
|
{{ pkg.manifest.title }}
|
||||||
</h1>
|
</h1>
|
||||||
<h2>{{ pkg.manifest.version | displayEmver }}</h2>
|
<h2>{{ pkg.manifest.version | displayEmver }}</h2>
|
||||||
|
|||||||
@@ -1,7 +1,3 @@
|
|||||||
.name {
|
|
||||||
font-family: 'Montserrat', sans-serif;
|
|
||||||
}
|
|
||||||
|
|
||||||
.less-large {
|
.less-large {
|
||||||
font-size: 18px !important;
|
font-size: 18px !important;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<h1 class="heading ion-text-center">{{ name }}</h1>
|
<h1 class="heading montserrat ion-text-center">{{ name }}</h1>
|
||||||
|
|
||||||
<marketplace-search [(query)]="query"></marketplace-search>
|
<marketplace-search [(query)]="query"></marketplace-search>
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
.heading {
|
.heading {
|
||||||
font-family: 'Montserrat', sans-serif;
|
|
||||||
font-size: 42px;
|
font-size: 42px;
|
||||||
margin: 32px 0;
|
margin: 32px 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Component } from '@angular/core'
|
import { Component } from '@angular/core'
|
||||||
import { defer, Observable } from 'rxjs'
|
import { Observable } from 'rxjs'
|
||||||
import { filter, first, map, startWith, switchMapTo, tap } from 'rxjs/operators'
|
import { filter, first, map, startWith, switchMapTo, tap } from 'rxjs/operators'
|
||||||
import { exists, isEmptyObject } from '@start9labs/shared'
|
import { exists, isEmptyObject } from '@start9labs/shared'
|
||||||
import {
|
import {
|
||||||
@@ -16,14 +16,13 @@ import { spreadProgress } from '../utils/spread-progress'
|
|||||||
templateUrl: './marketplace-list.page.html',
|
templateUrl: './marketplace-list.page.html',
|
||||||
})
|
})
|
||||||
export class MarketplaceListPage {
|
export class MarketplaceListPage {
|
||||||
readonly localPkgs$: Observable<Record<string, PackageDataEntry>> = defer(
|
readonly localPkgs$: Observable<Record<string, PackageDataEntry>> = this.patch
|
||||||
() => this.patch.watch$('package-data'),
|
.watch$('package-data')
|
||||||
).pipe(
|
.pipe(
|
||||||
filter(data => exists(data) && !isEmptyObject(data)),
|
filter(data => exists(data) && !isEmptyObject(data)),
|
||||||
tap(pkgs => Object.values(pkgs).forEach(spreadProgress)),
|
tap(pkgs => Object.values(pkgs).forEach(spreadProgress)),
|
||||||
map(pkgs => ({ ...pkgs })),
|
startWith({}),
|
||||||
startWith({}),
|
)
|
||||||
)
|
|
||||||
|
|
||||||
readonly categories$ = this.marketplaceService
|
readonly categories$ = this.marketplaceService
|
||||||
.getCategories()
|
.getCategories()
|
||||||
@@ -31,13 +30,13 @@ export class MarketplaceListPage {
|
|||||||
map(categories => new Set(['featured', 'updates', ...categories, 'all'])),
|
map(categories => new Set(['featured', 'updates', ...categories, 'all'])),
|
||||||
)
|
)
|
||||||
|
|
||||||
readonly pkgs$: Observable<MarketplacePkg[]> = defer(() =>
|
readonly pkgs$: Observable<MarketplacePkg[]> = this.patch
|
||||||
this.patch.watch$('server-info'),
|
.watch$('server-info')
|
||||||
).pipe(
|
.pipe(
|
||||||
filter(data => exists(data) && !isEmptyObject(data)),
|
filter(data => exists(data) && !isEmptyObject(data)),
|
||||||
first(),
|
first(),
|
||||||
switchMapTo(this.marketplaceService.getPackages()),
|
switchMapTo(this.marketplaceService.getPackages()),
|
||||||
)
|
)
|
||||||
|
|
||||||
readonly name$: Observable<string> = this.marketplaceService
|
readonly name$: Observable<string> = this.marketplaceService
|
||||||
.getMarketplace()
|
.getMarketplace()
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
<ion-item *ngIf="dependentInfo" lines="none" class="rec-item">
|
<ion-item *ngIf="dependentInfo" lines="none" class="rec-item">
|
||||||
<ion-label>
|
<ion-label>
|
||||||
<h2 class="heading">
|
<h2 class="heading">
|
||||||
<ion-text class="title">
|
<ion-text class="montserrat title">
|
||||||
{{ title }}
|
{{ title }}
|
||||||
</ion-text>
|
</ion-text>
|
||||||
</h2>
|
</h2>
|
||||||
|
|||||||
@@ -5,7 +5,6 @@
|
|||||||
|
|
||||||
.title {
|
.title {
|
||||||
margin: 5px;
|
margin: 5px;
|
||||||
font-family: 'Montserrat', sans-serif;
|
|
||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import { ServerShowPage } from './server-show.page'
|
|||||||
import { FormsModule } from '@angular/forms'
|
import { FormsModule } from '@angular/forms'
|
||||||
import { TextSpinnerComponentModule } from '@start9labs/shared'
|
import { TextSpinnerComponentModule } from '@start9labs/shared'
|
||||||
import { BadgeMenuComponentModule } from 'src/app/components/badge-menu-button/badge-menu.component.module'
|
import { BadgeMenuComponentModule } from 'src/app/components/badge-menu-button/badge-menu.component.module'
|
||||||
import { SnakePageModule } from 'src/app/modals/snake/snake.module'
|
|
||||||
|
|
||||||
const routes: Routes = [
|
const routes: Routes = [
|
||||||
{
|
{
|
||||||
@@ -23,7 +22,6 @@ const routes: Routes = [
|
|||||||
RouterModule.forChild(routes),
|
RouterModule.forChild(routes),
|
||||||
TextSpinnerComponentModule,
|
TextSpinnerComponentModule,
|
||||||
BadgeMenuComponentModule,
|
BadgeMenuComponentModule,
|
||||||
SnakePageModule,
|
|
||||||
],
|
],
|
||||||
declarations: [ServerShowPage],
|
declarations: [ServerShowPage],
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -7,6 +7,9 @@ import {
|
|||||||
debounceTime,
|
debounceTime,
|
||||||
finalize,
|
finalize,
|
||||||
mergeMap,
|
mergeMap,
|
||||||
|
skip,
|
||||||
|
switchMap,
|
||||||
|
take,
|
||||||
tap,
|
tap,
|
||||||
withLatestFrom,
|
withLatestFrom,
|
||||||
} from 'rxjs/operators'
|
} from 'rxjs/operators'
|
||||||
@@ -14,6 +17,7 @@ import { isEmptyObject, pauseFor } from '@start9labs/shared'
|
|||||||
import { DataModel } from './data-model'
|
import { DataModel } from './data-model'
|
||||||
import { ApiService } from '../api/embassy-api.service'
|
import { ApiService } from '../api/embassy-api.service'
|
||||||
import { AuthService } from '../auth.service'
|
import { AuthService } from '../auth.service'
|
||||||
|
import { patch } from '@start9labs/emver'
|
||||||
|
|
||||||
export const PATCH_HTTP = new InjectionToken<Source<DataModel>>('')
|
export const PATCH_HTTP = new InjectionToken<Source<DataModel>>('')
|
||||||
export const PATCH_SOURCE = new InjectionToken<Source<DataModel>>('')
|
export const PATCH_SOURCE = new InjectionToken<Source<DataModel>>('')
|
||||||
@@ -176,9 +180,19 @@ export class PatchDbService {
|
|||||||
|
|
||||||
// prettier-ignore
|
// prettier-ignore
|
||||||
watch$: Store<DataModel>['watch$'] = (...args: (string | number)[]): Observable<DataModel> => {
|
watch$: Store<DataModel>['watch$'] = (...args: (string | number)[]): Observable<DataModel> => {
|
||||||
|
// TODO: refactor with a better solution to race condition
|
||||||
const argsString = '/' + args.join('/')
|
const argsString = '/' + args.join('/')
|
||||||
|
const source$ =
|
||||||
|
this.patchDb?.store.watch$(...(args as [])) ||
|
||||||
|
this.patchConnection$.pipe(
|
||||||
|
skip(1),
|
||||||
|
take(1),
|
||||||
|
switchMap(() => this.patchDb.store.watch$(...(args as []))),
|
||||||
|
)
|
||||||
|
|
||||||
console.log('patchDB: WATCHING ', argsString)
|
console.log('patchDB: WATCHING ', argsString)
|
||||||
return this.patchDb.store.watch$(...(args as [])).pipe(
|
|
||||||
|
return source$.pipe(
|
||||||
tap(data => console.log('patchDB: NEW VALUE', argsString, data)),
|
tap(data => console.log('patchDB: NEW VALUE', argsString, data)),
|
||||||
catchError(e => {
|
catchError(e => {
|
||||||
console.error('patchDB: WATCH ERROR', e)
|
console.error('patchDB: WATCH ERROR', e)
|
||||||
|
|||||||
17
frontend/projects/ui/src/app/util/animations.ts
Normal file
17
frontend/projects/ui/src/app/util/animations.ts
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import { animate, style, transition, trigger } from '@angular/animations'
|
||||||
|
|
||||||
|
const TRANSITION = '{{duration}}ms {{delay}}ms ease-in-out'
|
||||||
|
const DURATION = { params: { duration: 300, delay: 0 } }
|
||||||
|
|
||||||
|
export const heightCollapse = trigger('heightCollapse', [
|
||||||
|
transition(
|
||||||
|
':enter',
|
||||||
|
[style({ height: 0 }), animate(TRANSITION, style({ height: '*' }))],
|
||||||
|
DURATION,
|
||||||
|
),
|
||||||
|
transition(
|
||||||
|
':leave',
|
||||||
|
[style({ height: '*' }), animate(TRANSITION, style({ height: 0 }))],
|
||||||
|
DURATION,
|
||||||
|
),
|
||||||
|
])
|
||||||
Reference in New Issue
Block a user