Modernise & deploy #1
6 changed files with 257 additions and 46 deletions
|
|
@ -7,10 +7,11 @@
|
||||||
@for (tower of page().towers; track tower.id) {
|
@for (tower of page().towers; track tower.id) {
|
||||||
<lt-tower
|
<lt-tower
|
||||||
cdkDrag
|
cdkDrag
|
||||||
[cdkDragDisabled]="modalOpen()"
|
[cdkDragDisabled]="modalOpen() || mobileDragDisabled()"
|
||||||
[tower]="tower"
|
[tower]="tower"
|
||||||
[dateRange]="dateRange()"
|
[dateRange]="dateRange()"
|
||||||
[keepTasksOpen]="page().keep_tasks_open"
|
[keepTasksOpen]="page().keep_tasks_open"
|
||||||
|
[animateInitialStack]="animateInitialStack()"
|
||||||
(cdkDragStarted)="onTowerDragStart(tower.id)"
|
(cdkDragStarted)="onTowerDragStart(tower.id)"
|
||||||
(updateTower)="onUpdateTower(tower.id, $event)"
|
(updateTower)="onUpdateTower(tower.id, $event)"
|
||||||
(deleteTowerRequest)="onDeleteTower(tower.id)"
|
(deleteTowerRequest)="onDeleteTower(tower.id)"
|
||||||
|
|
@ -21,7 +22,16 @@
|
||||||
}
|
}
|
||||||
@if (!page().hide_create_tower_button) {
|
@if (!page().hide_create_tower_button) {
|
||||||
<div class="add-tower-wrapper">
|
<div class="add-tower-wrapper">
|
||||||
<img class="add-tower" src="assets/plus-sign.svg" alt="Add tower" (click)="showAddTower.set(true)" />
|
<img
|
||||||
|
class="add-tower"
|
||||||
|
src="assets/plus-sign.svg"
|
||||||
|
alt="Add tower"
|
||||||
|
role="button"
|
||||||
|
tabindex="0"
|
||||||
|
(click)="showAddTower.set(true)"
|
||||||
|
(keydown.enter)="showAddTower.set(true)"
|
||||||
|
(keydown.space)="$event.preventDefault(); showAddTower.set(true)"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
</section>
|
</section>
|
||||||
|
|
@ -48,7 +58,7 @@
|
||||||
|
|
||||||
@if (showAddTower()) {
|
@if (showAddTower()) {
|
||||||
<lt-modal title="New Tower" (close)="showAddTower.set(false)">
|
<lt-modal title="New Tower" (close)="showAddTower.set(false)">
|
||||||
<lt-tower-settings [tower]="null" (save)="onAddTower($event)" />
|
<lt-tower-settings [tower]="null" (save)="onAddTower($event)" (close)="showAddTower.set(false)" />
|
||||||
</lt-modal>
|
</lt-modal>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -56,7 +66,7 @@
|
||||||
<lt-modal (close)="cancelTowerDelete()">
|
<lt-modal (close)="cancelTowerDelete()">
|
||||||
<div class="confirm-delete">
|
<div class="confirm-delete">
|
||||||
<div class="header">
|
<div class="header">
|
||||||
<div class="exit" (click)="cancelTowerDelete()" role="button" aria-label="Cancel"></div>
|
<button class="exit" type="button" (click)="cancelTowerDelete()" aria-label="Cancel"></button>
|
||||||
<h2>Delete tower</h2>
|
<h2>Delete tower</h2>
|
||||||
</div>
|
</div>
|
||||||
<p>Delete <strong>{{ confirmDeleteTowerName() || 'this tower' }}</strong> and all of its blocks? This can't be undone.</p>
|
<p>Delete <strong>{{ confirmDeleteTowerName() || 'this tower' }}</strong> and all of its blocks? This can't be undone.</p>
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,11 @@
|
||||||
@import '../../../styles';
|
@import '../../../library/main';
|
||||||
|
|
||||||
:host {
|
:host {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
min-height: 0;
|
||||||
position: relative; // anchor for absolute-positioned .trash
|
position: relative; // anchor for absolute-positioned .trash
|
||||||
|
|
||||||
@include inner-spacing(var(--large-padding));
|
@include inner-spacing(var(--large-padding));
|
||||||
|
|
@ -18,14 +19,17 @@
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
margin-right: auto;
|
margin-right: auto;
|
||||||
|
|
||||||
flex: 1 0 auto;
|
flex: 1 1 auto;
|
||||||
|
min-height: 0;
|
||||||
|
|
||||||
transition: box-shadow $short-animation-time;
|
transition: box-shadow $short-animation-time;
|
||||||
|
|
||||||
max-width: 800px;
|
max-width: 100%;
|
||||||
|
gap: var(--medium-padding);
|
||||||
|
|
||||||
&.cdk-drop-list-dragging {
|
&.cdk-drop-list-dragging {
|
||||||
*:not(.cdk-drag-placeholder) {
|
*:not(.cdk-drag-placeholder) {
|
||||||
|
|
@ -52,28 +56,45 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
& > * {
|
& > * {
|
||||||
|
width: 100%;
|
||||||
max-width: 200px;
|
max-width: 200px;
|
||||||
box-sizing: content-box;
|
box-sizing: border-box;
|
||||||
flex: 0 0 auto;
|
flex: 1 1 0;
|
||||||
|
min-width: 0;
|
||||||
&:not(:nth-last-child(1)) {
|
min-height: 0;
|
||||||
margin-right: var(--medium-padding);
|
|
||||||
@media (max-width: $mobile-width) {
|
|
||||||
margin-right: var(--small-padding);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
@for $i from 1 to 12 {
|
// Mobile: fixed-width towers with horizontal scroll (1.5-column rhythm).
|
||||||
& > *:first-child:nth-last-child(#{$i}),
|
|
||||||
& > *:first-child:nth-last-child(#{$i}) ~ * {
|
|
||||||
width: calc((100% - (#{$i} - 1) * var(--medium-padding)) / #{$i});
|
|
||||||
|
|
||||||
@media (max-width: $mobile-width) {
|
@media (max-width: $mobile-width) {
|
||||||
width: calc((100% - (#{$i} - 1) * var(--small-padding)) / #{$i});
|
--mobile-tower-width: calc(66vw - var(--small-padding));
|
||||||
|
--mobile-tower-side-padding: max(
|
||||||
|
var(--medium-padding),
|
||||||
|
calc((100% - var(--mobile-tower-width)) / 2)
|
||||||
|
);
|
||||||
|
|
||||||
|
overflow-x: auto;
|
||||||
|
overflow-y: visible;
|
||||||
|
-webkit-overflow-scrolling: touch;
|
||||||
|
scroll-snap-type: x mandatory;
|
||||||
|
scroll-padding-inline: var(--mobile-tower-side-padding);
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
justify-content: flex-start;
|
||||||
|
padding: 0 var(--mobile-tower-side-padding);
|
||||||
|
max-width: 100%;
|
||||||
|
gap: var(--medium-padding);
|
||||||
|
|
||||||
|
&::-webkit-scrollbar {
|
||||||
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
& > * {
|
||||||
|
width: var(--mobile-tower-width) !important;
|
||||||
|
max-width: var(--mobile-tower-width) !important;
|
||||||
|
min-width: var(--mobile-tower-width) !important;
|
||||||
|
scroll-snap-align: center;
|
||||||
|
flex: 0 0 var(--mobile-tower-width);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -89,7 +110,11 @@
|
||||||
@include card();
|
@include card();
|
||||||
width: 66vw;
|
width: 66vw;
|
||||||
max-width: 500px;
|
max-width: 500px;
|
||||||
@media (max-width: $mobile-width) { width: 300px; }
|
@media (max-width: $mobile-width) {
|
||||||
|
width: 88vw;
|
||||||
|
max-width: 88vw;
|
||||||
|
padding: var(--medium-padding);
|
||||||
|
}
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
padding: var(--large-padding);
|
padding: var(--large-padding);
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
@ -101,7 +126,7 @@
|
||||||
@include center-child();
|
@include center-child();
|
||||||
.exit {
|
.exit {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: var(--large-padding);
|
right: var(--large-padding);
|
||||||
@include exit();
|
@include exit();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -115,11 +140,21 @@
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
gap: var(--large-padding);
|
gap: var(--large-padding);
|
||||||
|
|
||||||
|
button {
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
button.danger {
|
button.danger {
|
||||||
color: #b53f3f;
|
color: #b53f3f;
|
||||||
border-bottom-color: #b53f3f55;
|
border-bottom-color: #b53f3f55;
|
||||||
&:after { background-color: #b53f3f; }
|
&:after { background-color: #b53f3f; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media (max-width: $mobile-width) {
|
||||||
|
flex-direction: column;
|
||||||
|
gap: var(--small-padding);
|
||||||
|
button { width: 100%; }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,11 @@ import {
|
||||||
input,
|
input,
|
||||||
output,
|
output,
|
||||||
signal,
|
signal,
|
||||||
|
computed,
|
||||||
inject,
|
inject,
|
||||||
|
HostListener,
|
||||||
|
effect,
|
||||||
|
untracked,
|
||||||
} from '@angular/core';
|
} from '@angular/core';
|
||||||
import { Page } from '../../models';
|
import { Page } from '../../models';
|
||||||
import { StoreService } from '../../services/store.service';
|
import { StoreService } from '../../services/store.service';
|
||||||
|
|
@ -15,7 +19,6 @@ import {
|
||||||
DoubleSliderComponent,
|
DoubleSliderComponent,
|
||||||
DoubleSliderRange,
|
DoubleSliderRange,
|
||||||
} from '../shared/double-slider/double-slider.component';
|
} from '../shared/double-slider/double-slider.component';
|
||||||
import { computed } from '@angular/core';
|
|
||||||
import { CdkDropList, CdkDrag, CdkDragDrop } from '@angular/cdk/drag-drop';
|
import { CdkDropList, CdkDrag, CdkDragDrop } from '@angular/cdk/drag-drop';
|
||||||
import { ModalStateService } from '../../services/modal-state.service';
|
import { ModalStateService } from '../../services/modal-state.service';
|
||||||
|
|
||||||
|
|
@ -38,6 +41,7 @@ interface BlockPatch {
|
||||||
tag: string;
|
tag: string;
|
||||||
description: string;
|
description: string;
|
||||||
is_done: boolean;
|
is_done: boolean;
|
||||||
|
difficulty: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Minimum blocks before the date-range slider becomes visible. */
|
/** Minimum blocks before the date-range slider becomes visible. */
|
||||||
|
|
@ -60,12 +64,14 @@ const MIN_BLOCKS_FOR_SLIDER = 2;
|
||||||
})
|
})
|
||||||
export class PageComponent {
|
export class PageComponent {
|
||||||
readonly page = input.required<Page>();
|
readonly page = input.required<Page>();
|
||||||
|
readonly animateInitialStack = input<boolean>(false);
|
||||||
readonly dragHappening = output<boolean>();
|
readonly dragHappening = output<boolean>();
|
||||||
|
|
||||||
protected readonly store = inject(StoreService);
|
protected readonly store = inject(StoreService);
|
||||||
private readonly modalState = inject(ModalStateService);
|
private readonly modalState = inject(ModalStateService);
|
||||||
/** True while any lt-modal is mounted — used to lock tower drag. */
|
/** True while any lt-modal is mounted — used to lock tower drag. */
|
||||||
readonly modalOpen = this.modalState.anyOpen;
|
readonly modalOpen = this.modalState.anyOpen;
|
||||||
|
readonly mobileDragDisabled = signal(this.isMobileViewport());
|
||||||
|
|
||||||
readonly showAddTower = signal(false);
|
readonly showAddTower = signal(false);
|
||||||
readonly isDragging = signal(false);
|
readonly isDragging = signal(false);
|
||||||
|
|
@ -112,10 +118,30 @@ export class PageComponent {
|
||||||
/** Selected date range — `null` = show everything. */
|
/** Selected date range — `null` = show everything. */
|
||||||
readonly dateRange = signal<{ from: number; to: number } | null>(null);
|
readonly dateRange = signal<{ from: number; to: number } | null>(null);
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
effect(() => {
|
||||||
|
if (!this.showSlider()) {
|
||||||
|
untracked(() => this.dateRange.set(null));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
onSliderRangeChange(range: DoubleSliderRange<unknown>): void {
|
onSliderRangeChange(range: DoubleSliderRange<unknown>): void {
|
||||||
this.dateRange.set({ from: range.from as number, to: range.to as number });
|
this.dateRange.set({ from: range.from as number, to: range.to as number });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@HostListener('window:resize')
|
||||||
|
onResize(): void {
|
||||||
|
this.mobileDragDisabled.set(this.isMobileViewport());
|
||||||
|
}
|
||||||
|
|
||||||
|
private isMobileViewport(): boolean {
|
||||||
|
return (
|
||||||
|
typeof window !== 'undefined' &&
|
||||||
|
window.matchMedia('(max-width: 520px), (pointer: coarse)').matches
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// ── Tower mutations ────────────────────────────────────────────────────────
|
// ── Tower mutations ────────────────────────────────────────────────────────
|
||||||
|
|
||||||
onAddTower(result: TowerSettingsResult): void {
|
onAddTower(result: TowerSettingsResult): void {
|
||||||
|
|
@ -131,7 +157,7 @@ export class PageComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
onDeleteTower(towerId: string): void {
|
onDeleteTower(towerId: string): void {
|
||||||
this.store.deleteTower(this.page().id, towerId);
|
this.confirmDeleteTowerId.set(towerId);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Block mutations ────────────────────────────────────────────────────────
|
// ── Block mutations ────────────────────────────────────────────────────────
|
||||||
|
|
@ -143,6 +169,7 @@ export class PageComponent {
|
||||||
result.tag,
|
result.tag,
|
||||||
result.description,
|
result.description,
|
||||||
result.is_done,
|
result.is_done,
|
||||||
|
result.difficulty,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -187,13 +214,16 @@ export class PageComponent {
|
||||||
|
|
||||||
onTrashEnter(): void {
|
onTrashEnter(): void {
|
||||||
this.nearTrashcan = true;
|
this.nearTrashcan = true;
|
||||||
const preview = document.querySelector('.cdk-drag-preview');
|
this.dragPreview()?.classList.add('trash-highlight');
|
||||||
if (preview) preview.classList.add('trash-highlight');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onTrashLeave(): void {
|
onTrashLeave(): void {
|
||||||
this.nearTrashcan = false;
|
this.nearTrashcan = false;
|
||||||
const preview = document.querySelector('.cdk-drag-preview');
|
this.dragPreview()?.classList.remove('trash-highlight');
|
||||||
if (preview) preview.classList.remove('trash-highlight');
|
}
|
||||||
|
|
||||||
|
/** The CDK drag preview currently in flight, if any. Matches legacy DOM-driven trash highlight. */
|
||||||
|
private dragPreview(): Element | null {
|
||||||
|
return document.querySelector('.cdk-drag-preview');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,11 @@
|
||||||
|
|
||||||
<div class="page-container">
|
<div class="page-container">
|
||||||
@if (selectedPage(); as page) {
|
@if (selectedPage(); as page) {
|
||||||
<lt-page [page]="page" (dragHappening)="dragHappening.set($event)" />
|
<lt-page
|
||||||
|
[page]="page"
|
||||||
|
[animateInitialStack]="page.id === animateInitialStackPageId()"
|
||||||
|
(dragHappening)="dragHappening.set($event)"
|
||||||
|
/>
|
||||||
} @else {
|
} @else {
|
||||||
<p>Add a new page to get started!</p>
|
<p>Add a new page to get started!</p>
|
||||||
}
|
}
|
||||||
|
|
@ -27,14 +31,37 @@
|
||||||
[page]="selectedPage()"
|
[page]="selectedPage()"
|
||||||
(close)="showSettings.set(false)"
|
(close)="showSettings.set(false)"
|
||||||
(updatePage)="onUpdatePage($event)"
|
(updatePage)="onUpdatePage($event)"
|
||||||
(deletePage)="onRemovePage()"
|
(deletePage)="onRequestRemovePage()"
|
||||||
(switchAccount)="onSwitchAccount($event)"
|
(switchAccount)="onSwitchAccount($event)"
|
||||||
/>
|
/>
|
||||||
</lt-modal>
|
</lt-modal>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@if (confirmDeletePageId()) {
|
||||||
|
<lt-modal (close)="cancelRemovePage()">
|
||||||
|
<div class="confirm-delete">
|
||||||
|
<div class="header">
|
||||||
|
<button class="exit" type="button" (click)="cancelRemovePage()" aria-label="Cancel"></button>
|
||||||
|
<h2>Delete page</h2>
|
||||||
|
</div>
|
||||||
|
<p>
|
||||||
|
Delete <strong>{{ confirmDeletePageName() || 'this page' }}</strong> and all of its towers and blocks?
|
||||||
|
This can't be undone.
|
||||||
|
</p>
|
||||||
|
<div class="confirm-buttons">
|
||||||
|
<button type="button" (click)="cancelRemovePage()">Cancel</button>
|
||||||
|
<button type="button" class="danger" (click)="confirmRemovePage()">Delete page</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</lt-modal>
|
||||||
|
}
|
||||||
|
|
||||||
@if (showWelcome()) {
|
@if (showWelcome()) {
|
||||||
<lt-modal (close)="showWelcome.set(false)">
|
<lt-modal
|
||||||
|
labelledBy="welcome-title"
|
||||||
|
describedBy="welcome-description"
|
||||||
|
(close)="showWelcome.set(false)"
|
||||||
|
>
|
||||||
<lt-welcome
|
<lt-welcome
|
||||||
(close)="showWelcome.set(false)"
|
(close)="showWelcome.set(false)"
|
||||||
(startFresh)="showWelcome.set(false)"
|
(startFresh)="showWelcome.set(false)"
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,8 @@
|
||||||
@import '../../../styles';
|
@import '../../../library/main';
|
||||||
|
|
||||||
:host {
|
:host {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
min-height: 0;
|
||||||
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|
@ -11,14 +12,24 @@
|
||||||
|
|
||||||
.select-add-container {
|
.select-add-container {
|
||||||
width: 250px;
|
width: 250px;
|
||||||
margin-left: auto;
|
margin: 0 auto;
|
||||||
margin-right: auto;
|
|
||||||
position: relative;
|
position: relative;
|
||||||
z-index: 1000;
|
z-index: 1000;
|
||||||
|
@media (max-width: $mobile-width) {
|
||||||
|
width: 80vw;
|
||||||
|
max-width: 320px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.page-container {
|
.page-container {
|
||||||
flex: 1 0 auto;
|
flex: 1 1 auto;
|
||||||
|
min-height: 0;
|
||||||
|
// Generous breathing room between the page selector dropdown and the
|
||||||
|
// towers — the dropdown can open downward without crowding the towers.
|
||||||
|
padding-top: var(--large-padding);
|
||||||
|
@media (max-width: $mobile-width) {
|
||||||
|
padding-top: var(--medium-padding);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
button {
|
button {
|
||||||
|
|
@ -27,5 +38,65 @@
|
||||||
&.transparent {
|
&.transparent {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media (max-width: $mobile-width) {
|
||||||
|
margin-top: var(--medium-padding);
|
||||||
|
font-size: var(--medium-font-size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.confirm-delete {
|
||||||
|
@include card();
|
||||||
|
width: 66vw;
|
||||||
|
max-width: 500px;
|
||||||
|
@media (max-width: $mobile-width) {
|
||||||
|
width: 88vw;
|
||||||
|
max-width: 88vw;
|
||||||
|
padding: var(--medium-padding);
|
||||||
|
}
|
||||||
|
box-sizing: border-box;
|
||||||
|
padding: var(--large-padding);
|
||||||
|
position: relative;
|
||||||
|
box-shadow: $shadow;
|
||||||
|
@include inner-spacing(var(--large-padding));
|
||||||
|
text-align: center;
|
||||||
|
|
||||||
|
.header {
|
||||||
|
@include center-child();
|
||||||
|
|
||||||
|
.exit {
|
||||||
|
position: absolute;
|
||||||
|
right: var(--large-padding);
|
||||||
|
@include exit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.confirm-buttons {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
gap: var(--large-padding);
|
||||||
|
|
||||||
|
button {
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
button.danger {
|
||||||
|
color: #b53f3f;
|
||||||
|
border-bottom-color: #b53f3f55;
|
||||||
|
|
||||||
|
&:after {
|
||||||
|
background-color: #b53f3f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: $mobile-width) {
|
||||||
|
flex-direction: column;
|
||||||
|
gap: var(--small-padding);
|
||||||
|
|
||||||
|
button {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import {
|
||||||
signal,
|
signal,
|
||||||
computed,
|
computed,
|
||||||
effect,
|
effect,
|
||||||
|
OnDestroy,
|
||||||
} from '@angular/core';
|
} from '@angular/core';
|
||||||
import { StoreService } from '../../services/store.service';
|
import { StoreService } from '../../services/store.service';
|
||||||
import { PageComponent } from '../page/page.component';
|
import { PageComponent } from '../page/page.component';
|
||||||
|
|
@ -22,7 +23,7 @@ import { Page } from '../../models';
|
||||||
templateUrl: './pages.component.html',
|
templateUrl: './pages.component.html',
|
||||||
styleUrl: './pages.component.scss',
|
styleUrl: './pages.component.scss',
|
||||||
})
|
})
|
||||||
export class PagesComponent {
|
export class PagesComponent implements OnDestroy {
|
||||||
protected readonly store = inject(StoreService);
|
protected readonly store = inject(StoreService);
|
||||||
|
|
||||||
/** ID of currently selected page within store.pages(). */
|
/** ID of currently selected page within store.pages(). */
|
||||||
|
|
@ -31,22 +32,43 @@ export class PagesComponent {
|
||||||
readonly showSettings = signal(false);
|
readonly showSettings = signal(false);
|
||||||
readonly dragHappening = signal(false);
|
readonly dragHappening = signal(false);
|
||||||
readonly showWelcome = signal(false);
|
readonly showWelcome = signal(false);
|
||||||
|
readonly confirmDeletePageId = signal<string | null>(null);
|
||||||
|
readonly animateInitialStackPageId = signal<string | null>(null);
|
||||||
|
private exampleAnimationTimer: ReturnType<typeof setTimeout> | null = null;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
effect(() => {
|
effect(() => {
|
||||||
if (!this.store.loading() && this.store.pages().length === 0) {
|
const pages = this.store.pages();
|
||||||
|
if (!this.store.loading() && pages.length === 0) {
|
||||||
this.showWelcome.set(true);
|
this.showWelcome.set(true);
|
||||||
} else if (this.store.pages().length > 0) {
|
} else if (pages.length > 0) {
|
||||||
this.showWelcome.set(false);
|
this.showWelcome.set(false);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
onLoadExample(): void {
|
onLoadExample(): void {
|
||||||
this.store.loadExample();
|
const pageId = this.store.loadExample();
|
||||||
|
this.selectedPageId.set(pageId);
|
||||||
|
this.animateInitialStackPageId.set(pageId);
|
||||||
|
if (this.exampleAnimationTimer !== null) {
|
||||||
|
clearTimeout(this.exampleAnimationTimer);
|
||||||
|
}
|
||||||
|
this.exampleAnimationTimer = setTimeout(() => {
|
||||||
|
if (this.animateInitialStackPageId() === pageId) {
|
||||||
|
this.animateInitialStackPageId.set(null);
|
||||||
|
}
|
||||||
|
this.exampleAnimationTimer = null;
|
||||||
|
}, 2500);
|
||||||
this.showWelcome.set(false);
|
this.showWelcome.set(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ngOnDestroy(): void {
|
||||||
|
if (this.exampleAnimationTimer !== null) {
|
||||||
|
clearTimeout(this.exampleAnimationTimer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
readonly pageNames = computed(() => this.store.pages().map((p) => p.name));
|
readonly pageNames = computed(() => this.store.pages().map((p) => p.name));
|
||||||
|
|
||||||
readonly selectedPage = computed<Page | null>(() => {
|
readonly selectedPage = computed<Page | null>(() => {
|
||||||
|
|
@ -61,12 +83,17 @@ export class PagesComponent {
|
||||||
return pages[0] ?? null;
|
return pages[0] ?? null;
|
||||||
});
|
});
|
||||||
|
|
||||||
readonly selectedPageName = computed(() => this.selectedPage()?.name ?? null);
|
readonly confirmDeletePageName = computed(() => {
|
||||||
|
const id = this.confirmDeletePageId();
|
||||||
|
if (!id) return '';
|
||||||
|
return this.store.pages().find((p) => p.id === id)?.name ?? '';
|
||||||
|
});
|
||||||
|
|
||||||
readonly selectedPageIndex = computed(() => {
|
readonly selectedPageIndex = computed(() => {
|
||||||
|
const pages = this.store.pages();
|
||||||
const page = this.selectedPage();
|
const page = this.selectedPage();
|
||||||
if (!page) return -1;
|
if (!page) return -1;
|
||||||
return this.store.pages().findIndex((p) => p.id === page.id);
|
return pages.findIndex((p) => p.id === page.id);
|
||||||
});
|
});
|
||||||
|
|
||||||
onSelectPage(index: number): void {
|
onSelectPage(index: number): void {
|
||||||
|
|
@ -94,12 +121,23 @@ export class PagesComponent {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onRemovePage(): void {
|
onRequestRemovePage(): void {
|
||||||
const page = this.selectedPage();
|
const page = this.selectedPage();
|
||||||
if (!page) return;
|
if (!page) return;
|
||||||
this.store.deletePage(page.id);
|
this.confirmDeletePageId.set(page.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
confirmRemovePage(): void {
|
||||||
|
const pageId = this.confirmDeletePageId();
|
||||||
|
if (!pageId) return;
|
||||||
|
this.store.deletePage(pageId);
|
||||||
this.selectedPageId.set(null);
|
this.selectedPageId.set(null);
|
||||||
this.showSettings.set(false);
|
this.showSettings.set(false);
|
||||||
|
this.confirmDeletePageId.set(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
cancelRemovePage(): void {
|
||||||
|
this.confirmDeletePageId.set(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
onSwitchAccount(token: string): void {
|
onSwitchAccount(token: string): void {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue