first chrome implementation

This commit is contained in:
DEV Sam Hayes
2025-01-10 19:37:10 +01:00
parent dc7a980dc5
commit a652718bc7
175 changed files with 18526 additions and 610 deletions

View File

@@ -0,0 +1,28 @@
<!-- eslint-disable @angular-eslint/template/elements-content -->
<div [id]="idString" class="modal fade" data-bs-backdrop="static" tabindex="-1">
<div
class="modal-dialog modal-sm modal-dialog-centered modal-dialog-scrollable"
>
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Please confirm</h5>
<button
type="button"
class="btn-close"
(click)="modal?.hide(); no.emit()"
></button>
</div>
<div class="modal-body">
<span>{{ message }}</span>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" (click)="modal?.hide()">
No
</button>
<button type="button" class="btn btn-primary" (click)="onClickYes()">
Yes
</button>
</div>
</div>
</div>
</div>

View File

@@ -0,0 +1,5 @@
:host {
.modal {
--bs-modal-margin: 32px;
}
}

View File

@@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ConfirmComponent } from './confirm.component';
describe('ConfirmComponent', () => {
let component: ConfirmComponent;
let fixture: ComponentFixture<ConfirmComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [ConfirmComponent]
})
.compileComponents();
fixture = TestBed.createComponent(ConfirmComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -0,0 +1,42 @@
import { AfterViewInit, Component, EventEmitter, Output } from '@angular/core';
import * as bootstrap from 'bootstrap';
@Component({
// eslint-disable-next-line @angular-eslint/component-selector
selector: 'lib-confirm',
imports: [],
templateUrl: './confirm.component.html',
styleUrl: './confirm.component.scss',
})
export class ConfirmComponent implements AfterViewInit {
@Output() yes = new EventEmitter<void>();
@Output() no = new EventEmitter<void>();
message: string | undefined;
onYes: ((() => Promise<void>) | (() => void)) | undefined;
modal: bootstrap.Modal | undefined;
readonly idString = crypto.randomUUID();
ngAfterViewInit(): void {
const myModalEl = document.getElementById(this.idString);
if (!myModalEl) {
return;
}
this.modal = new bootstrap.Modal(myModalEl);
}
onClickYes() {
this.modal?.hide();
if (typeof this.onYes !== 'undefined') {
this.onYes();
}
}
show(message: string, onYes: (() => Promise<void>) | (() => void)): void {
this.message = message;
this.onYes = onYes;
this.modal?.show();
}
}

View File

@@ -0,0 +1,3 @@
<div class="icon-button">
<i [class]="'bi bi-' + icon"></i>
</div>

View File

@@ -0,0 +1,18 @@
:host {
.icon-button {
width: 32px;
height: 32px;
display: flex;
align-items: center;
justify-content: center;
//padding: 10px;
border-radius: 100%;
cursor: pointer;
&:hover {
background: gray;
}
}
}

View File

@@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { IconButtonComponent } from './icon-button.component';
describe('IconButtonComponent', () => {
let component: IconButtonComponent;
let fixture: ComponentFixture<IconButtonComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [IconButtonComponent]
})
.compileComponents();
fixture = TestBed.createComponent(IconButtonComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -0,0 +1,12 @@
import { Component, Input } from '@angular/core';
@Component({
// eslint-disable-next-line @angular-eslint/component-selector
selector: 'lib-icon-button',
imports: [],
templateUrl: './icon-button.component.html',
styleUrl: './icon-button.component.scss',
})
export class IconButtonComponent {
@Input({ required: true }) icon!: string;
}

View File

@@ -0,0 +1,12 @@
<span class="sam-no-select text">
{{ text }}
</span>
<div class="sam-flex-grow"></div>
<div class="buttons sam-flex-row gap-h">
<lib-icon-button
icon="arrow-right"
style="pointer-events: none"
></lib-icon-button>
</div>

View File

@@ -0,0 +1,23 @@
:host {
cursor: pointer;
height: 48px;
min-height: 48px;
display: flex;
flex-direction: row;
align-items: center;
padding-left: 16px;
padding-right: 8px;
background: var(--background-light);
border-radius: 8px;
margin-bottom: 8px;
&:hover {
background: var(--background-light-hover);
}
.text {
overflow-x: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}

View File

@@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { NavItemComponent } from './nav-item.component';
describe('NavItemComponent', () => {
let component: NavItemComponent;
let fixture: ComponentFixture<NavItemComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [NavItemComponent]
})
.compileComponents();
fixture = TestBed.createComponent(NavItemComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -0,0 +1,13 @@
import { Component, Input } from '@angular/core';
import { IconButtonComponent } from "../icon-button/icon-button.component";
@Component({
// eslint-disable-next-line @angular-eslint/component-selector
selector: 'lib-nav-item',
imports: [IconButtonComponent],
templateUrl: './nav-item.component.html',
styleUrl: './nav-item.component.scss',
})
export class NavItemComponent {
@Input({ required: true }) text!: string;
}

View File

@@ -0,0 +1,3 @@
<span [style.color]="color">{{ npubString }}</span>
<lib-icon-button icon="copy" (click)="copyToClipboard()"></lib-icon-button>

View File

@@ -0,0 +1,6 @@
:host {
display: flex;
flex-direction: row;
align-items: center;
column-gap: calc(var(--size) / 2);
}

View File

@@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { PubkeyComponent } from './pubkey.component';
describe('PubkeyComponent', () => {
let component: PubkeyComponent;
let fixture: ComponentFixture<PubkeyComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [PubkeyComponent]
})
.compileComponents();
fixture = TestBed.createComponent(PubkeyComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -0,0 +1,38 @@
import { Component, Input, OnInit } from '@angular/core';
import { NostrHelper } from '@common';
import { IconButtonComponent } from "../icon-button/icon-button.component";
@Component({
// eslint-disable-next-line @angular-eslint/component-selector
selector: 'lib-pubkey',
imports: [IconButtonComponent],
templateUrl: './pubkey.component.html',
styleUrl: './pubkey.component.scss',
})
export class PubkeyComponent implements OnInit {
@Input({ required: true }) value!: string;
@Input() first = 9;
@Input() last = 5;
@Input() color = '#dee2e6bf';
npub: string | undefined;
npubString: string | undefined;
ngOnInit(): void {
const pubkeyObject = NostrHelper.getNostrPubkeyObject(this.value);
this.npub = pubkeyObject.npub;
this.npubString = NostrHelper.splitKey(
pubkeyObject.npub,
this.first,
this.last
);
}
copyToClipboard() {
if (!this.npub) {
return;
}
navigator.clipboard.writeText(this.npub);
}
}

View File

@@ -0,0 +1 @@
<span>{{ type }}</span>

View File

@@ -0,0 +1,31 @@
:host {
border-radius: 4px;
padding: 0px 4px;
font-size: 12px;
text-align: center;
border: 1px solid transparent;
min-width: 40px;
min-height: 20px;
cursor: pointer;
&.read {
&:not(.is-selected) {
border: 1px solid var(--bs-green);
}
&.is-selected {
background: var(--bs-green);
}
}
&:not(.read) {
&:not(.is-selected) {
border: 1px solid var(--bs-primary);
}
&.is-selected {
background: var(--bs-primary);
}
}
}

View File

@@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { RelayRwComponent } from './relay-rw.component';
describe('RelayRwComponent', () => {
let component: RelayRwComponent;
let fixture: ComponentFixture<RelayRwComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [RelayRwComponent]
})
.compileComponents();
fixture = TestBed.createComponent(RelayRwComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -0,0 +1,34 @@
import {
Component,
EventEmitter,
HostBinding,
HostListener,
Input,
Output,
} from '@angular/core';
@Component({
// eslint-disable-next-line @angular-eslint/component-selector
selector: 'lib-relay-rw',
imports: [],
templateUrl: './relay-rw.component.html',
styleUrl: './relay-rw.component.scss',
})
export class RelayRwComponent {
@Input({ required: true }) type!: 'read' | 'write';
@Input({ required: true }) model!: boolean;
@Output() modelChange = new EventEmitter<boolean>();
@HostBinding('class.read') get isRead() {
return this.type === 'read';
}
@HostBinding('class.is-selected') get isSelected() {
return this.model;
}
@HostListener('click') onClick() {
this.model = !this.model;
this.modelChange.emit(this.model);
}
}

View File

@@ -0,0 +1,8 @@
<div
[id]="idString"
class="toast hide"
style="width: 100%"
role="alert"
>
<div class="toast-body">{{ message }}</div>
</div>

View File

@@ -0,0 +1,5 @@
:host {
position: absolute;
align-self: center;
//bottom: 76px;
}

View File

@@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ToastComponent } from './toast.component';
describe('ToastComponent', () => {
let component: ToastComponent;
let fixture: ComponentFixture<ToastComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [ToastComponent]
})
.compileComponents();
fixture = TestBed.createComponent(ToastComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -0,0 +1,37 @@
import { AfterViewInit, Component, HostBinding, Input } from '@angular/core';
import * as bootstrap from 'bootstrap';
@Component({
// eslint-disable-next-line @angular-eslint/component-selector
selector: 'lib-toast',
imports: [],
templateUrl: './toast.component.html',
styleUrl: './toast.component.scss',
})
export class ToastComponent implements AfterViewInit {
@Input() message: string | undefined;
@Input()
@HostBinding('style.bottom.px')
bottom = 76;
readonly idString = crypto.randomUUID();
toast: bootstrap.Toast | undefined;
ngAfterViewInit(): void {
const myToastEl = document.getElementById(this.idString);
if (!myToastEl) {
return;
}
this.toast = new bootstrap.Toast(myToastEl, { delay: 2000 });
}
show(message?: string) {
if (message) {
this.message = message;
}
this.toast?.show();
}
}