snapshot
This commit is contained in:
parent
3ad2766f82
commit
f74ee43cb4
54 changed files with 16749 additions and 29973 deletions
43
frontend/src/app/components/block/block.component.ts
Normal file
43
frontend/src/app/components/block/block.component.ts
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
import { Component, ChangeDetectionStrategy, input, output, computed } from '@angular/core';
|
||||
import { Block, HslColor } from '../../models';
|
||||
import { getColorOfTag } from '../../utils/color';
|
||||
|
||||
/**
|
||||
* A block rendered as a small COLORED SQUARE (1/6 of tower width).
|
||||
* Only DONE blocks appear here; pending blocks appear in the tasks accordion.
|
||||
* Clicking opens the block-edit modal in the parent tower component.
|
||||
*/
|
||||
@Component({
|
||||
selector: 'lt-block',
|
||||
standalone: true,
|
||||
imports: [],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
template: `
|
||||
<div [style.background-color]="color()" (click)="clicked.emit()"></div>
|
||||
`,
|
||||
styles: `
|
||||
@import '../../../library/main';
|
||||
|
||||
:host {
|
||||
position: relative;
|
||||
width: calc(100% / 6);
|
||||
padding-bottom: calc(100% / 6);
|
||||
|
||||
div {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
@include gravitate();
|
||||
}
|
||||
}
|
||||
`,
|
||||
})
|
||||
export class BlockComponent {
|
||||
readonly block = input.required<Block>();
|
||||
readonly baseColor = input.required<HslColor>();
|
||||
|
||||
/** Emits when the square is clicked — parent opens the block-edit modal. */
|
||||
readonly clicked = output<void>();
|
||||
|
||||
readonly color = computed(() => getColorOfTag(this.block().tag, this.baseColor()));
|
||||
}
|
||||
521
frontend/src/app/components/modal/block-edit.component.ts
Normal file
521
frontend/src/app/components/modal/block-edit.component.ts
Normal file
|
|
@ -0,0 +1,521 @@
|
|||
import {
|
||||
Component,
|
||||
ChangeDetectionStrategy,
|
||||
input,
|
||||
output,
|
||||
inject,
|
||||
signal,
|
||||
computed,
|
||||
effect,
|
||||
viewChild,
|
||||
ElementRef,
|
||||
AfterViewInit,
|
||||
HostListener,
|
||||
untracked,
|
||||
} from '@angular/core';
|
||||
import { Block, HslColor } from '../../models';
|
||||
import { SelectAddComponent } from '../shared/select-add/select-add.component';
|
||||
import { ToggleComponent } from '../shared/toggle/toggle.component';
|
||||
import { getColorOfTag } from '../../utils/color';
|
||||
|
||||
export interface BlockEditSave {
|
||||
/** null = create a new block */
|
||||
id: string | null;
|
||||
tag: string;
|
||||
description: string;
|
||||
is_done: boolean;
|
||||
}
|
||||
|
||||
interface EditedValue {
|
||||
tag: string;
|
||||
description: string;
|
||||
is_done: boolean;
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'lt-block-edit',
|
||||
standalone: true,
|
||||
imports: [SelectAddComponent, ToggleComponent],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
template: `
|
||||
<section
|
||||
#container
|
||||
class="carousel"
|
||||
(scroll)="onScroll()"
|
||||
(click)="onBackdropClick($event)"
|
||||
>
|
||||
<div class="card placeholder"></div>
|
||||
|
||||
@for (b of blocks(); track b.id; let i = $index) {
|
||||
<div
|
||||
class="card"
|
||||
[class.active]="activeIdx() === i + 1"
|
||||
[class.near-active]="activeIdx() === i || activeIdx() === i + 2"
|
||||
(click)="onCardClick(i + 1)"
|
||||
>
|
||||
<div class="mask"></div>
|
||||
|
||||
<div class="header">
|
||||
<div class="exit" (click)="close.emit(); $event.stopPropagation()"></div>
|
||||
<div
|
||||
class="block-dot"
|
||||
[style.background-color]="colorOfTagForBlock(b.id)"
|
||||
></div>
|
||||
<h1>{{ formatDate(b.created_at) }}</h1>
|
||||
</div>
|
||||
|
||||
<div class="select-add-container">
|
||||
<lt-select-add
|
||||
[items]="tags()"
|
||||
[selected]="editedFor(b.id).tag"
|
||||
[alwaysDropShadow]="true"
|
||||
[onlyShadowBorder]="true"
|
||||
placeholder="Tag this item…"
|
||||
(select)="updateTag(b.id, $event)"
|
||||
(add)="updateTag(b.id, $event)"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<textarea
|
||||
placeholder="Write a description here…"
|
||||
[value]="editedFor(b.id).description"
|
||||
(input)="updateDescription(b.id, $any($event.target).value)"
|
||||
(blur)="flushExisting(b.id)"
|
||||
></textarea>
|
||||
|
||||
<div>
|
||||
<lt-toggle
|
||||
[checked]="editedFor(b.id).is_done"
|
||||
(checkedChange)="updateDone(b.id, $event)"
|
||||
offLabel="This task hasn't been finished yet"
|
||||
onLabel="Goal already accomplished"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="bottom">
|
||||
<button (click)="onDelete(b.id); $event.stopPropagation()">Delete</button>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
<div
|
||||
class="card create-card"
|
||||
[class.active]="activeIdx() === blocks().length + 1"
|
||||
[class.near-active]="activeIdx() === blocks().length"
|
||||
(click)="onCardClick(blocks().length + 1)"
|
||||
>
|
||||
<div class="mask"></div>
|
||||
|
||||
<div class="header">
|
||||
<div class="exit" (click)="close.emit(); $event.stopPropagation()"></div>
|
||||
<div
|
||||
class="block-dot"
|
||||
[style.background-color]="colorOfNewTag()"
|
||||
></div>
|
||||
<h1>Create now</h1>
|
||||
</div>
|
||||
|
||||
<div class="select-add-container">
|
||||
<lt-select-add
|
||||
[items]="tags()"
|
||||
[selected]="newValue().tag"
|
||||
[alwaysDropShadow]="true"
|
||||
[onlyShadowBorder]="true"
|
||||
placeholder="Set a category…"
|
||||
(select)="updateNewTag($event)"
|
||||
(add)="updateNewTag($event)"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<textarea
|
||||
placeholder="Write a description here…"
|
||||
[value]="newValue().description"
|
||||
(input)="updateNewDescription($any($event.target).value)"
|
||||
></textarea>
|
||||
|
||||
<div>
|
||||
<lt-toggle
|
||||
[checked]="newValue().is_done"
|
||||
(checkedChange)="updateNewDone($event)"
|
||||
offLabel="This task hasn't been finished yet"
|
||||
onLabel="Goal already accomplished"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="bottom">
|
||||
<button
|
||||
(click)="submitNew(); $event.stopPropagation()"
|
||||
[disabled]="!newValue().tag"
|
||||
>
|
||||
Create and exit
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card placeholder"></div>
|
||||
</section>
|
||||
`,
|
||||
styles: `
|
||||
@import '../../../library/main';
|
||||
|
||||
:host {
|
||||
@include center-child();
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
z-index: 10001; // above modal backdrop (10000)
|
||||
}
|
||||
|
||||
.carousel {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
box-sizing: border-box;
|
||||
overflow-x: auto;
|
||||
overflow-y: hidden;
|
||||
scroll-behavior: smooth;
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
width: 0;
|
||||
height: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.card {
|
||||
@include card();
|
||||
box-shadow: $shadow;
|
||||
display: block;
|
||||
transform-origin: center center;
|
||||
flex: 0 0 auto;
|
||||
width: 66vw;
|
||||
max-width: 400px;
|
||||
@media (max-width: $mobile-width) {
|
||||
width: 300px;
|
||||
opacity: 1 !important;
|
||||
}
|
||||
box-sizing: border-box;
|
||||
padding: var(--large-padding);
|
||||
margin: calc(var(--large-padding) / 2);
|
||||
position: relative;
|
||||
@include inner-spacing(var(--large-padding));
|
||||
|
||||
opacity: 0.6;
|
||||
transition: opacity $long-animation-time;
|
||||
|
||||
&.near-active {
|
||||
cursor: pointer;
|
||||
opacity: 0.85;
|
||||
}
|
||||
|
||||
&.active {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.mask {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 10000;
|
||||
@include card();
|
||||
opacity: 1;
|
||||
transition: opacity $long-animation-time;
|
||||
pointer-events: none;
|
||||
@media (max-width: $mobile-width) {
|
||||
opacity: 0 !important;
|
||||
}
|
||||
}
|
||||
|
||||
&.active .mask {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
&.near-active .mask {
|
||||
opacity: 0.55;
|
||||
}
|
||||
|
||||
&:first-child {
|
||||
margin-left: var(--large-padding);
|
||||
}
|
||||
|
||||
&.placeholder {
|
||||
opacity: 0 !important;
|
||||
width: 60vw;
|
||||
max-width: 60vw;
|
||||
box-shadow: none;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.header {
|
||||
@include center-child();
|
||||
position: relative;
|
||||
|
||||
.exit {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
@include exit();
|
||||
}
|
||||
|
||||
.block-dot {
|
||||
@include square(12px);
|
||||
margin-right: 10px;
|
||||
border-radius: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
.select-add-container {
|
||||
// When the create card has no tag chosen, glow the dropdown red as a
|
||||
// gentle "required" cue — matches the legacy ghost-button affordance.
|
||||
&.required-empty lt-select-add {
|
||||
box-shadow: 0 0 0 0.75px rgba(181, 63, 63, 0.5);
|
||||
border-radius: var(--border-radius);
|
||||
}
|
||||
}
|
||||
|
||||
.bottom {
|
||||
height: 32px;
|
||||
@media (max-width: $mobile-width) {
|
||||
height: 24px;
|
||||
}
|
||||
position: relative;
|
||||
|
||||
button {
|
||||
margin: 0;
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
transform: translateY(-50%) translateX(-50%);
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
})
|
||||
export class BlockEditComponent implements AfterViewInit {
|
||||
readonly blocks = input.required<Block[]>();
|
||||
readonly activeBlockId = input<string | null>(null);
|
||||
readonly tags = input<string[]>([]);
|
||||
readonly baseColor = input.required<HslColor>();
|
||||
/** Default for `is_done` on the create card. */
|
||||
readonly defaultDone = input<boolean>(true);
|
||||
|
||||
readonly save = output<BlockEditSave>();
|
||||
readonly delete = output<string>();
|
||||
readonly close = output<void>();
|
||||
|
||||
private readonly container =
|
||||
viewChild<ElementRef<HTMLElement>>('container');
|
||||
|
||||
// Per-block edited values, keyed by block ID.
|
||||
readonly editedValues = signal<Map<string, EditedValue>>(new Map());
|
||||
|
||||
// Pending new block being authored on the create card.
|
||||
readonly newValue = signal<EditedValue>({
|
||||
tag: '',
|
||||
description: '',
|
||||
is_done: true,
|
||||
});
|
||||
|
||||
// 1-based index of the centered card. 0/N+2 are placeholders.
|
||||
readonly activeIdx = signal(1);
|
||||
|
||||
private scrollToken = 0;
|
||||
|
||||
constructor() {
|
||||
// Seed editedValues from input blocks (and re-seed if input changes).
|
||||
effect(() => {
|
||||
const bs = this.blocks();
|
||||
const m = new Map<string, EditedValue>();
|
||||
for (const b of bs) {
|
||||
m.set(b.id, {
|
||||
tag: b.tag,
|
||||
description: b.description,
|
||||
is_done: b.is_done,
|
||||
});
|
||||
}
|
||||
untracked(() => this.editedValues.set(m));
|
||||
});
|
||||
|
||||
// Seed the newValue tag from tags input on first run.
|
||||
effect(() => {
|
||||
const t = this.tags();
|
||||
untracked(() => {
|
||||
const cur = this.newValue();
|
||||
if (!cur.tag && t.length > 0) {
|
||||
this.newValue.set({ ...cur, tag: t[0], is_done: this.defaultDone() });
|
||||
} else if (!cur.tag) {
|
||||
this.newValue.set({ ...cur, is_done: this.defaultDone() });
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
ngAfterViewInit(): void {
|
||||
// Position scroll on the focused card (or the create card if none).
|
||||
queueMicrotask(() => {
|
||||
const blocks = this.blocks();
|
||||
const focusId = this.activeBlockId();
|
||||
const focusIdx = focusId
|
||||
? Math.max(0, blocks.findIndex((b) => b.id === focusId))
|
||||
: blocks.length;
|
||||
this.scrollToChild(focusIdx + 1, false);
|
||||
});
|
||||
}
|
||||
|
||||
// ── Helpers ────────────────────────────────────────────────────────────────
|
||||
|
||||
editedFor(id: string): EditedValue {
|
||||
return (
|
||||
this.editedValues().get(id) ?? {
|
||||
tag: '',
|
||||
description: '',
|
||||
is_done: false,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
colorOfTagForBlock(id: string): string {
|
||||
const v = this.editedFor(id);
|
||||
return v.tag ? getColorOfTag(v.tag, this.baseColor()) : 'transparent';
|
||||
}
|
||||
|
||||
colorOfNewTag = computed(() => {
|
||||
const t = this.newValue().tag;
|
||||
return t ? getColorOfTag(t, this.baseColor()) : 'transparent';
|
||||
});
|
||||
|
||||
formatDate(ts: number): string {
|
||||
const d = new Date(ts * 1000);
|
||||
return d.toLocaleDateString(undefined, {
|
||||
year: 'numeric',
|
||||
month: 'short',
|
||||
day: 'numeric',
|
||||
});
|
||||
}
|
||||
|
||||
// ── Existing-card mutations (auto-save on each change) ─────────────────────
|
||||
|
||||
updateTag(id: string, tag: string): void {
|
||||
this.patchEdited(id, { tag });
|
||||
this.flushExisting(id);
|
||||
}
|
||||
|
||||
updateDescription(id: string, description: string): void {
|
||||
this.patchEdited(id, { description });
|
||||
// Description flush deferred to (blur) to avoid PUT per keystroke.
|
||||
}
|
||||
|
||||
updateDone(id: string, is_done: boolean): void {
|
||||
this.patchEdited(id, { is_done });
|
||||
this.flushExisting(id);
|
||||
}
|
||||
|
||||
private patchEdited(id: string, patch: Partial<EditedValue>): void {
|
||||
this.editedValues.update((m) => {
|
||||
const v = m.get(id);
|
||||
if (!v) return m;
|
||||
const next = new Map(m);
|
||||
next.set(id, { ...v, ...patch });
|
||||
return next;
|
||||
});
|
||||
}
|
||||
|
||||
flushExisting(id: string): void {
|
||||
const v = this.editedFor(id);
|
||||
if (!v.tag) return; // Skip empty saves
|
||||
this.save.emit({ id, tag: v.tag, description: v.description, is_done: v.is_done });
|
||||
}
|
||||
|
||||
onDelete(id: string): void {
|
||||
this.delete.emit(id);
|
||||
}
|
||||
|
||||
// ── Create-card mutations ──────────────────────────────────────────────────
|
||||
|
||||
updateNewTag(tag: string): void {
|
||||
this.newValue.update((v) => ({ ...v, tag }));
|
||||
}
|
||||
|
||||
updateNewDescription(description: string): void {
|
||||
this.newValue.update((v) => ({ ...v, description }));
|
||||
}
|
||||
|
||||
updateNewDone(is_done: boolean): void {
|
||||
this.newValue.update((v) => ({ ...v, is_done }));
|
||||
}
|
||||
|
||||
submitNew(): void {
|
||||
const v = this.newValue();
|
||||
if (!v.tag) return;
|
||||
this.save.emit({
|
||||
id: null,
|
||||
tag: v.tag,
|
||||
description: v.description,
|
||||
is_done: v.is_done,
|
||||
});
|
||||
this.close.emit();
|
||||
}
|
||||
|
||||
// ── Scroll handling ────────────────────────────────────────────────────────
|
||||
|
||||
onCardClick(idx: number): void {
|
||||
if (idx !== this.activeIdx()) {
|
||||
this.scrollToChild(idx, true);
|
||||
}
|
||||
}
|
||||
|
||||
/** Close the carousel when the user clicks anywhere that isn't a real card. */
|
||||
onBackdropClick(event: MouseEvent): void {
|
||||
const target = event.target as HTMLElement | null;
|
||||
if (target && !target.closest('.card:not(.placeholder)')) {
|
||||
this.close.emit();
|
||||
}
|
||||
}
|
||||
|
||||
onScroll(): void {
|
||||
const token = ++this.scrollToken;
|
||||
setTimeout(() => {
|
||||
if (token === this.scrollToken) this.adjustPosition();
|
||||
}, 150);
|
||||
}
|
||||
|
||||
@HostListener('window:resize')
|
||||
onResize(): void {
|
||||
this.scrollToChild(this.activeIdx(), false);
|
||||
}
|
||||
|
||||
private scrollToChild(idx: number, smooth: boolean): void {
|
||||
const container = this.container()?.nativeElement;
|
||||
if (!container) return;
|
||||
const card = container.children.item(idx) as HTMLElement | null;
|
||||
if (!card) return;
|
||||
const left =
|
||||
card.offsetLeft - (container.clientWidth - card.offsetWidth) / 2;
|
||||
container.scrollTo({ left, behavior: smooth ? 'smooth' : 'auto' });
|
||||
this.activeIdx.set(idx);
|
||||
}
|
||||
|
||||
private adjustPosition(): void {
|
||||
const container = this.container()?.nativeElement;
|
||||
if (!container) return;
|
||||
const center = container.scrollLeft + container.clientWidth / 2;
|
||||
let nearestIdx = 1;
|
||||
let minDist = Infinity;
|
||||
// children[0] and children[last] are the placeholders — skip.
|
||||
for (let i = 1; i < container.children.length - 1; i++) {
|
||||
const child = container.children.item(i) as HTMLElement;
|
||||
const c = child.offsetLeft + child.offsetWidth / 2;
|
||||
const d = Math.abs(c - center);
|
||||
if (d < minDist) {
|
||||
minDist = d;
|
||||
nearestIdx = i;
|
||||
}
|
||||
}
|
||||
if (nearestIdx !== this.activeIdx()) {
|
||||
this.scrollToChild(nearestIdx, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
<section
|
||||
(click)="modalService.cancel()"
|
||||
class="{{ modalService.active ? 'active' : '' }}"
|
||||
[ngSwitch]="modalService.active?.type"
|
||||
>
|
||||
<app-blocks (save)="save = $event" (click)="$event.stopPropagation()" *ngSwitchCase="ModalType.blocks"></app-blocks>
|
||||
<app-remove-tower (click)="$event.stopPropagation()" *ngSwitchCase="ModalType.removeTower"></app-remove-tower>
|
||||
<app-settings (click)="$event.stopPropagation()" *ngSwitchCase="ModalType.settings"></app-settings>
|
||||
<app-get-started (click)="$event.stopPropagation()" *ngSwitchCase="ModalType.getStarted"></app-get-started>
|
||||
<app-remove-page (click)="$event.stopPropagation()" *ngSwitchCase="ModalType.removePage"></app-remove-page>
|
||||
</section>
|
||||
|
|
@ -1,29 +0,0 @@
|
|||
@import '../../../styles';
|
||||
|
||||
section {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
|
||||
z-index: 10000;
|
||||
|
||||
@include center-child();
|
||||
|
||||
padding: var(--large-padding);
|
||||
|
||||
box-sizing: border-box;
|
||||
|
||||
background: $background-gradient;
|
||||
transition: opacity 300ms;
|
||||
|
||||
&:not(.active) {
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
button {
|
||||
margin-top: var(--medium-padding);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,25 +1,94 @@
|
|||
import { Component } from '@angular/core';
|
||||
import { ModalService, ModalType } from '../../services/modal.service';
|
||||
import { CancelService } from '../../services/cancel.service';
|
||||
import {
|
||||
Component,
|
||||
ChangeDetectionStrategy,
|
||||
output,
|
||||
signal,
|
||||
AfterViewInit,
|
||||
OnDestroy,
|
||||
viewChild,
|
||||
ElementRef,
|
||||
inject,
|
||||
} from '@angular/core';
|
||||
import { A11yModule } from '@angular/cdk/a11y';
|
||||
import { ModalStateService } from '../../services/modal-state.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-modal',
|
||||
templateUrl: './modal.component.html',
|
||||
styleUrls: ['./modal.component.scss']
|
||||
})
|
||||
export class ModalComponent {
|
||||
ModalType = ModalType;
|
||||
selector: 'lt-modal',
|
||||
standalone: true,
|
||||
imports: [A11yModule],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
template: `
|
||||
<section
|
||||
class="modal"
|
||||
[class.active]="active()"
|
||||
(click)="onBackdropClick($event)"
|
||||
>
|
||||
<div class="modal__dialog" #dialog cdkTrapFocus cdkTrapFocusAutoCapture (keydown.escape)="onClose()">
|
||||
<ng-content></ng-content>
|
||||
</div>
|
||||
</section>
|
||||
`,
|
||||
styles: `
|
||||
@import '../../../library/main';
|
||||
|
||||
save: () => void = null;
|
||||
section.modal {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
z-index: 10000;
|
||||
@include center-child();
|
||||
padding: var(--large-padding);
|
||||
box-sizing: border-box;
|
||||
background: $background-gradient;
|
||||
transition: opacity 300ms;
|
||||
opacity: 1;
|
||||
|
||||
constructor(public modalService: ModalService, private cancelService: CancelService) {
|
||||
this.cancelService.subscribe(this, () => {
|
||||
if (this.save) {
|
||||
this.save();
|
||||
this.save = null;
|
||||
} else {
|
||||
this.modalService.cancel();
|
||||
&:not(.active) {
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
});
|
||||
|
||||
button {
|
||||
margin-top: var(--medium-padding);
|
||||
}
|
||||
}
|
||||
`,
|
||||
})
|
||||
export class ModalComponent implements AfterViewInit, OnDestroy {
|
||||
readonly close = output<void>();
|
||||
|
||||
// The active signal starts false; AfterViewInit flips it true on next tick
|
||||
// so the 300ms opacity transition fires on entry (0 → 1).
|
||||
readonly active = signal(false);
|
||||
|
||||
private readonly dialogRef = viewChild<ElementRef<HTMLElement>>('dialog');
|
||||
private previousFocus: HTMLElement | null = null;
|
||||
private escListener!: (e: KeyboardEvent) => void;
|
||||
private readonly modalState = inject(ModalStateService);
|
||||
|
||||
ngAfterViewInit(): void {
|
||||
this.previousFocus = document.activeElement as HTMLElement;
|
||||
// Track open state so towers can be locked while any modal is mounted.
|
||||
this.modalState.open();
|
||||
// Defer one tick so the opacity transition runs (0 → 1).
|
||||
setTimeout(() => this.active.set(true), 0);
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.modalState.close();
|
||||
this.previousFocus?.focus();
|
||||
}
|
||||
|
||||
onBackdropClick(event: MouseEvent): void {
|
||||
const dialog = this.dialogRef()?.nativeElement;
|
||||
if (dialog && !dialog.contains(event.target as Node)) {
|
||||
this.onClose();
|
||||
}
|
||||
}
|
||||
|
||||
onClose(): void {
|
||||
this.close.emit();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,85 +0,0 @@
|
|||
<section #container *ngIf="tower">
|
||||
<div class="card placeholder"></div>
|
||||
<div
|
||||
*ngFor="let i of range({ max: blocks.length })"
|
||||
(click)="$event.stopPropagation(); scrollToChild(i + 1)"
|
||||
class="card {{ i + 1 === activeChild ? 'active' : '' }} {{
|
||||
i + 2 === activeChild || i === activeChild ? 'near-active' : ''
|
||||
}}"
|
||||
>
|
||||
<div class="mask"></div>
|
||||
|
||||
<div class="header">
|
||||
<div class="exit" (click)="modalService.cancel()"></div>
|
||||
<div class="block" [ngStyle]="{ 'background-color': tower.getColorOfTag(editedValues[i].tag) | color }"></div>
|
||||
<h1 [innerText]="editedValues[i]?.created | formatDate"></h1>
|
||||
</div>
|
||||
|
||||
<div class="select-add-container">
|
||||
<app-select-add
|
||||
class="select"
|
||||
[options]="tower.tags"
|
||||
[default]="editedValues[i].tag"
|
||||
[alwaysDropShadow]="true"
|
||||
[onlyShadowBorder]="true"
|
||||
[placeholder]="'Tag this item…'"
|
||||
(value)="editedValues[i].tag = $event"
|
||||
></app-select-add>
|
||||
</div>
|
||||
|
||||
<textarea placeholder="Write a description here…" [(ngModel)]="editedValues[i].description"></textarea>
|
||||
|
||||
<div>
|
||||
<app-toggle
|
||||
[beforeText]="'This task hasn\'t been finished yet'"
|
||||
[afterText]="'Goal already accomplished'"
|
||||
[default]="blocks[i].isDone"
|
||||
(value)="editedValues[i].isDone = $event"
|
||||
></app-toggle>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
(click)="$event.stopPropagation(); scrollToChild(blocks.length + 1)"
|
||||
class="card {{ blocks.length + 1 === activeChild ? 'active' : '' }} {{
|
||||
blocks.length === activeChild ? 'near-active' : ''
|
||||
}} "
|
||||
>
|
||||
<div class="mask"></div>
|
||||
|
||||
<div class="header">
|
||||
<div class="exit" (click)="modalService.cancel()"></div>
|
||||
<div class="block" [ngStyle]="{ 'background-color': tower.getColorOfTag(top(editedValues).tag) | color }"></div>
|
||||
<h1>Create now</h1>
|
||||
</div>
|
||||
|
||||
<div class="select-add-container">
|
||||
<app-select-add
|
||||
class="select"
|
||||
[options]="tower.tags"
|
||||
[default]="tower.tags.length ? tower.tags[0] : null"
|
||||
[alwaysDropShadow]="true"
|
||||
[onlyShadowBorder]="true"
|
||||
[placeholder]="'Set a category…'"
|
||||
[newValuePlaceholder]="'Add a category…'"
|
||||
(value)="top(editedValues).tag = $event"
|
||||
></app-select-add>
|
||||
</div>
|
||||
|
||||
<textarea placeholder="Write a description here…" [(ngModel)]="top(editedValues).description"></textarea>
|
||||
|
||||
<div>
|
||||
<app-toggle
|
||||
[beforeText]="'This task hasn\'t been finished yet'"
|
||||
[afterText]="'Goal already accomplished'"
|
||||
[default]="onlyDone"
|
||||
(value)="top(editedValues).isDone = $event"
|
||||
></app-toggle>
|
||||
</div>
|
||||
|
||||
<div class="bottom">
|
||||
<button (click)="submitAdd()" [disabled]="!top(editedValues).tag">Create and exit</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card placeholder"></div>
|
||||
</section>
|
||||
|
|
@ -1,175 +0,0 @@
|
|||
@import '../../../../../styles';
|
||||
|
||||
:host {
|
||||
@include center-child();
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
overflow-x: auto;
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
width: 0;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
section {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
box-sizing: border-box;
|
||||
}
|
||||
}
|
||||
|
||||
.card {
|
||||
@include card();
|
||||
box-shadow: $shadow;
|
||||
display: block;
|
||||
|
||||
transform-origin: center center;
|
||||
|
||||
flex: 0 0 auto;
|
||||
width: 66vw;
|
||||
max-width: 400px;
|
||||
@media (max-width: $mobile-width) {
|
||||
width: 300px;
|
||||
opacity: 1 !important;
|
||||
}
|
||||
|
||||
box-sizing: border-box;
|
||||
padding: var(--large-padding);
|
||||
margin: calc(var(--large-padding) / 2);
|
||||
position: relative;
|
||||
|
||||
&.near-active {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.mask {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 10000;
|
||||
|
||||
@include card();
|
||||
|
||||
@media (max-width: $mobile-width) {
|
||||
opacity: 0 !important;
|
||||
}
|
||||
}
|
||||
|
||||
&:first-child {
|
||||
margin-left: var(--large-padding);
|
||||
}
|
||||
|
||||
&.placeholder {
|
||||
opacity: 0 !important;
|
||||
width: 60vw;
|
||||
max-width: 60vw;
|
||||
}
|
||||
|
||||
@include inner-spacing(var(--large-padding));
|
||||
|
||||
.header {
|
||||
@include center-child();
|
||||
position: relative;
|
||||
|
||||
.exit {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
|
||||
@include exit();
|
||||
}
|
||||
|
||||
.block {
|
||||
@include square(12px);
|
||||
margin-right: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.bottom {
|
||||
height: 32px;
|
||||
@media (max-width: $mobile-width) {
|
||||
height: 24px;
|
||||
}
|
||||
|
||||
position: relative;
|
||||
|
||||
button {
|
||||
margin: 0;
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
transform: translateY(-50%) translateX(-50%);
|
||||
|
||||
transition: opacity $short-animation-time;
|
||||
|
||||
&.hidden {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.edit {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
opacity: 0.25;
|
||||
cursor: pointer;
|
||||
|
||||
img {
|
||||
@include square(16px);
|
||||
}
|
||||
|
||||
transition: opacity $short-animation-time;
|
||||
|
||||
&:before {
|
||||
content: '';
|
||||
display: block;
|
||||
position: absolute;
|
||||
bottom: calc(-1 * #{$line-height});
|
||||
left: 0;
|
||||
height: $line-height;
|
||||
background-color: $text-color;
|
||||
width: 0;
|
||||
transition: width $long-animation-time;
|
||||
}
|
||||
|
||||
@media (min-width: $mobile-width) {
|
||||
&:hover {
|
||||
opacity: 0.5;
|
||||
}
|
||||
&:hover {
|
||||
&:before {
|
||||
width: 100% !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.active {
|
||||
&:before {
|
||||
width: 100% !important;
|
||||
}
|
||||
}
|
||||
|
||||
&.active {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.card:last-child:after {
|
||||
content: '';
|
||||
height: 1px;
|
||||
width: var(--large-padding);
|
||||
right: calc(-1 * var(--large-padding));
|
||||
display: block;
|
||||
position: absolute;
|
||||
}
|
||||
|
|
@ -1,183 +0,0 @@
|
|||
import {
|
||||
ChangeDetectorRef,
|
||||
Component,
|
||||
ElementRef,
|
||||
EventEmitter,
|
||||
HostListener,
|
||||
OnDestroy,
|
||||
OnInit,
|
||||
Output,
|
||||
ViewChild
|
||||
} from '@angular/core';
|
||||
import { ModalService } from '../../../../services/modal.service';
|
||||
import { Tower } from '../../../../model/tower';
|
||||
import { Observable } from 'rxjs/internal/Observable';
|
||||
import { Block } from '../../../../model/block';
|
||||
import { IBlock } from '../../../../interfaces/persistance/block';
|
||||
import { CancelService } from '../../../../services/cancel.service';
|
||||
import { range } from 'src/app/utils/range';
|
||||
import { top } from 'src/app/utils/top';
|
||||
|
||||
@Component({
|
||||
selector: 'app-blocks',
|
||||
templateUrl: './blocks.component.html',
|
||||
styleUrls: ['./blocks.component.scss']
|
||||
})
|
||||
export class BlocksComponent implements OnInit, OnDestroy {
|
||||
readonly range = range;
|
||||
readonly top = top;
|
||||
tower: Tower;
|
||||
editedValues: Array<Partial<IBlock>>;
|
||||
endOfScrollToken = 0;
|
||||
activeChild: number;
|
||||
scrollMayEnd = true;
|
||||
onlyDone: boolean;
|
||||
@ViewChild('container') container: ElementRef;
|
||||
|
||||
private intervalID: number;
|
||||
|
||||
constructor(
|
||||
public modalService: ModalService,
|
||||
private cancelService: CancelService,
|
||||
private changeDetector: ChangeDetectorRef,
|
||||
private component: ElementRef
|
||||
) {
|
||||
window.addEventListener('resize', this.onScroll.bind(this));
|
||||
}
|
||||
|
||||
@Output() save: EventEmitter<() => void> = new EventEmitter();
|
||||
|
||||
get blocks(): Array<Block> {
|
||||
return this.tower.blocks.filter(b => b.isDone === this.onlyDone);
|
||||
}
|
||||
|
||||
@HostListener('click') cancel() {
|
||||
this.cancelService.cancelAll();
|
||||
}
|
||||
|
||||
@HostListener('touchstart') fingerDown() {
|
||||
this.scrollMayEnd = false;
|
||||
}
|
||||
|
||||
@HostListener('touchend') fingerUp() {
|
||||
this.scrollMayEnd = true;
|
||||
this.onScroll();
|
||||
}
|
||||
|
||||
@HostListener('scroll') onScroll() {
|
||||
const newToken = ++this.endOfScrollToken;
|
||||
setTimeout(() => {
|
||||
if (newToken === this.endOfScrollToken && this.scrollMayEnd) {
|
||||
this.adjustPosition();
|
||||
}
|
||||
}, 150);
|
||||
this.animateScroll();
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
const {
|
||||
tower$,
|
||||
onlyDone,
|
||||
startBlock
|
||||
}: { tower$: Observable<Tower>; onlyDone: boolean; startBlock: Block } = this.modalService.active.input;
|
||||
|
||||
this.save.emit(() => this.submitChange());
|
||||
|
||||
this.intervalID = setInterval(() => this.changeDetector.detectChanges(), 1000);
|
||||
|
||||
this.onlyDone = onlyDone;
|
||||
const subscription = tower$.subscribe(value => {
|
||||
if (value) {
|
||||
this.tower = value;
|
||||
this.editedValues = this.blocks.map(({ isDone, description, tag, created }) => ({
|
||||
isDone,
|
||||
description,
|
||||
tag,
|
||||
created
|
||||
}));
|
||||
this.editedValues.push({
|
||||
tag: this.tower.tags.length ? this.tower.tags[0] : null,
|
||||
isDone: this.onlyDone,
|
||||
description: ''
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
this.scrollToChild(startBlock ? this.blocks.indexOf(startBlock) + 1 : this.blocks.length + 1, true);
|
||||
subscription.unsubscribe();
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
animateScroll() {
|
||||
if (!this.container || !this.component) {
|
||||
return;
|
||||
}
|
||||
|
||||
const c = this.component.nativeElement;
|
||||
|
||||
[...this.container.nativeElement.children]
|
||||
.slice(1, -1)
|
||||
.forEach(element =>
|
||||
this.animate(
|
||||
element.style,
|
||||
element.querySelector('.mask').style,
|
||||
Math.abs(element.offsetLeft - c.scrollLeft + element.clientWidth / 2 - window.innerWidth / 2) /
|
||||
element.clientWidth
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
animate(cardStyle, maskStyle, t: number) {
|
||||
t = Math.min(2, Math.max(0, t));
|
||||
cardStyle.opacity = (1.33 * (1 - t / 2)).toString();
|
||||
t = Math.min(1, Math.max(0, t));
|
||||
maskStyle.opacity = Math.pow(t, 0.5).toString();
|
||||
maskStyle.display = t <= 0.05 ? 'none' : 'block';
|
||||
}
|
||||
|
||||
adjustPosition() {
|
||||
if (!this.container || !this.component) {
|
||||
return;
|
||||
}
|
||||
|
||||
const c = this.component.nativeElement;
|
||||
|
||||
const middle =
|
||||
[...this.container.nativeElement.children]
|
||||
.slice(1, -1)
|
||||
.map(element => Math.abs(element.offsetLeft - c.scrollLeft + element.clientWidth / 2 - window.innerWidth / 2))
|
||||
.map((value, index) => (Math.abs(index + 1 - this.activeChild) === 1 ? Math.abs(value - 100) : value))
|
||||
.reduce(
|
||||
(middleIndex, current, currentIndex, list) => (list[middleIndex] < current ? middleIndex : currentIndex),
|
||||
0
|
||||
) + 1;
|
||||
|
||||
this.scrollToChild(middle);
|
||||
}
|
||||
|
||||
scrollToChild(index: number, instantly?: boolean) {
|
||||
this.activeChild = index;
|
||||
const element = this.container.nativeElement.children[index];
|
||||
|
||||
this.component.nativeElement.scrollTo({
|
||||
left: element.offsetLeft - (window.innerWidth / 2 - element.clientWidth / 2),
|
||||
behavior: instantly ? 'auto' : 'smooth'
|
||||
});
|
||||
}
|
||||
|
||||
submitAdd() {
|
||||
top(this.editedValues).created = new Date();
|
||||
this.tower.addBlock(top(this.editedValues) as IBlock);
|
||||
this.cancelService.cancelAll();
|
||||
}
|
||||
|
||||
submitChange() {
|
||||
this.blocks.forEach((b, i) => b.changeKeys(this.editedValues[i]));
|
||||
this.modalService.submit();
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
clearInterval(this.intervalID);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
<p>
|
||||
get-started works!
|
||||
</p>
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
import { Component, OnInit } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-get-started',
|
||||
templateUrl: './get-started.component.html',
|
||||
styleUrls: ['./get-started.component.scss']
|
||||
})
|
||||
export class GetStartedComponent implements OnInit {
|
||||
constructor() {}
|
||||
|
||||
ngOnInit() {}
|
||||
}
|
||||
|
|
@ -1,13 +0,0 @@
|
|||
<section>
|
||||
<div class="header">
|
||||
<div class="exit" (click)="modalService.cancel()"></div>
|
||||
<h1>Are you sure?</h1>
|
||||
</div>
|
||||
|
||||
<p>
|
||||
You are trying to remove <strong>{{ this.modalService.active.input }}</strong
|
||||
>.
|
||||
</p>
|
||||
|
||||
<button (click)="modalService.submit()">Remove</button>
|
||||
</section>
|
||||
|
|
@ -1,30 +0,0 @@
|
|||
@import '../../../../../styles';
|
||||
|
||||
section {
|
||||
@include card();
|
||||
|
||||
width: 66vw;
|
||||
max-width: 500px;
|
||||
@media (max-width: $mobile-width) {
|
||||
width: 300px;
|
||||
}
|
||||
|
||||
box-sizing: border-box;
|
||||
padding: var(--large-padding);
|
||||
|
||||
position: relative;
|
||||
box-shadow: $shadow;
|
||||
|
||||
@include inner-spacing(var(--large-padding));
|
||||
|
||||
.header {
|
||||
@include center-child();
|
||||
|
||||
.exit {
|
||||
position: absolute;
|
||||
left: var(--large-padding);
|
||||
|
||||
@include exit();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
import { Component } from '@angular/core';
|
||||
import { ModalService } from '../../../../services/modal.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-remove-page',
|
||||
templateUrl: './remove-page.component.html',
|
||||
styleUrls: ['./remove-page.component.scss']
|
||||
})
|
||||
export class RemovePageComponent {
|
||||
constructor(public modalService: ModalService) {}
|
||||
}
|
||||
|
|
@ -1,14 +0,0 @@
|
|||
<section>
|
||||
<div class="header">
|
||||
<div class="exit" (click)="modalService.cancel()"></div>
|
||||
<h1>Are you sure?</h1>
|
||||
</div>
|
||||
|
||||
<p>
|
||||
You are trying to remove
|
||||
<span [ngStyle]="{ color: tower.baseColor | color }">{{ tower.name ? tower.name : 'an unnamed tower' }}</span
|
||||
>.
|
||||
</p>
|
||||
|
||||
<button (click)="modalService.submit()">Remove</button>
|
||||
</section>
|
||||
|
|
@ -1,30 +0,0 @@
|
|||
@import '../../../../../styles';
|
||||
|
||||
section {
|
||||
@include card();
|
||||
|
||||
width: 66vw;
|
||||
max-width: 500px;
|
||||
@media (max-width: $mobile-width) {
|
||||
width: 300px;
|
||||
}
|
||||
|
||||
box-sizing: border-box;
|
||||
padding: var(--large-padding);
|
||||
|
||||
position: relative;
|
||||
box-shadow: $shadow;
|
||||
|
||||
@include inner-spacing(var(--large-padding));
|
||||
|
||||
.header {
|
||||
@include center-child();
|
||||
|
||||
.exit {
|
||||
position: absolute;
|
||||
left: var(--large-padding);
|
||||
|
||||
@include exit();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,14 +0,0 @@
|
|||
import { Component } from '@angular/core';
|
||||
import { ModalService } from '../../../../services/modal.service';
|
||||
import { Tower } from '../../../../model/tower';
|
||||
|
||||
@Component({
|
||||
selector: 'app-remove-tower',
|
||||
templateUrl: './remove-tower.component.html',
|
||||
styleUrls: ['./remove-tower.component.scss']
|
||||
})
|
||||
export class RemoveTowerComponent {
|
||||
constructor(public modalService: ModalService) {}
|
||||
|
||||
tower: Tower = this.modalService.active.input;
|
||||
}
|
||||
|
|
@ -1,21 +0,0 @@
|
|||
<div class="header">
|
||||
<div class="exit" (click)="modalService.cancel()"></div>
|
||||
<h1>Settings</h1>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<app-toggle
|
||||
[beforeText]="'Hide create tower button'"
|
||||
[afterText]="'Show create tower button'"
|
||||
[default]="!page.userData.hideCreateTowerButton"
|
||||
(value)="page.setHideCreateTowerButton(!$event)"
|
||||
></app-toggle>
|
||||
</div>
|
||||
|
||||
<p *ngIf="page.towers.length == 5">There can be a maximum of <strong>5</strong> towers on each page.</p>
|
||||
|
||||
<input id="token" type="text" [(ngModel)]="token" />
|
||||
|
||||
<button (click)="setNewToken()">Set token</button>
|
||||
|
||||
<button (click)="$event.stopPropagation() || deletePage()">Delete current page</button>
|
||||
|
|
@ -1,42 +0,0 @@
|
|||
@import '../../../../../styles';
|
||||
|
||||
:host {
|
||||
@include card();
|
||||
|
||||
width: 66vw;
|
||||
max-width: 400px;
|
||||
@media (max-width: $mobile-width) {
|
||||
width: 300px;
|
||||
}
|
||||
|
||||
box-sizing: border-box;
|
||||
padding: var(--large-padding);
|
||||
|
||||
position: relative;
|
||||
box-shadow: $shadow;
|
||||
|
||||
@include inner-spacing(var(--large-padding));
|
||||
|
||||
.header {
|
||||
@include center-child();
|
||||
|
||||
.exit {
|
||||
position: absolute;
|
||||
left: var(--large-padding);
|
||||
|
||||
@include exit();
|
||||
}
|
||||
}
|
||||
|
||||
p {
|
||||
font-size: var(--medium-font-size);
|
||||
}
|
||||
|
||||
input[type='text'] {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
button {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,56 +0,0 @@
|
|||
import { Component, OnDestroy, OnInit } from '@angular/core';
|
||||
import { ModalService } from '../../../../services/modal.service';
|
||||
import { DataService } from '../../../../services/data.service';
|
||||
import { Page } from '../../../../model/page';
|
||||
import { Data } from '../../../../model/data';
|
||||
import { Subscription } from 'rxjs/internal/Subscription';
|
||||
import { MapStoreService } from '../../../../services/map-store.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-settings',
|
||||
templateUrl: './settings.component.html',
|
||||
styleUrls: ['./settings.component.scss']
|
||||
})
|
||||
export class SettingsComponent implements OnInit, OnDestroy {
|
||||
data: Data;
|
||||
page: Page;
|
||||
|
||||
private dataSubscription: Subscription;
|
||||
private pageSubscription: Subscription;
|
||||
|
||||
token: string;
|
||||
|
||||
constructor(public modalService: ModalService, private store: MapStoreService) {
|
||||
this.token = store.userToken;
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
const { data$, page$ } = this.modalService.active.input;
|
||||
|
||||
this.dataSubscription = data$.subscribe(d => (this.data = d));
|
||||
this.pageSubscription = page$.subscribe(p => (this.page = p));
|
||||
}
|
||||
|
||||
async deletePage() {
|
||||
try {
|
||||
await this.modalService.showRemovePage(this.page.name);
|
||||
this.data.removePage(this.page);
|
||||
this.modalService.submit();
|
||||
} catch {
|
||||
// pass
|
||||
}
|
||||
}
|
||||
|
||||
setNewToken() {
|
||||
this.store.userToken = this.token;
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
if (this.dataSubscription) {
|
||||
this.dataSubscription.unsubscribe();
|
||||
}
|
||||
if (this.pageSubscription) {
|
||||
this.pageSubscription.unsubscribe();
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue