This commit is contained in:
Andras Schmelczer 2026-05-30 14:47:25 +01:00
parent 003f38ea60
commit f87480e79d
7 changed files with 124 additions and 36 deletions

View file

@ -5,7 +5,8 @@
"packageManager": "npm",
"schematicCollections": [
"angular-eslint"
]
],
"analytics": false
},
"newProjectRoot": "projects",
"projects": {

View file

@ -74,6 +74,7 @@ interface EditedValue {
[alwaysDropShadow]="true"
[onlyShadowBorder]="true"
placeholder="Tag this item…"
emptyHint="No tags yet. Type one below to create it."
(select)="updateTag(b.id, $event)"
(add)="updateTag(b.id, $event)"
/>
@ -125,6 +126,7 @@ interface EditedValue {
[alwaysDropShadow]="true"
[onlyShadowBorder]="true"
placeholder="Set a category…"
emptyHint="No tags yet. Type one below to create it."
(select)="updateNewTag($event)"
(add)="updateNewTag($event)"
/>

View file

@ -28,7 +28,8 @@
transition: box-shadow $short-animation-time;
max-width: 800px;
max-width: 100%;
gap: var(--medium-padding);
&.cdk-drop-list-dragging {
*:not(.cdk-drag-placeholder) {
@ -55,38 +56,17 @@
}
& > * {
width: 100%;
max-width: 200px;
box-sizing: content-box;
flex: 0 0 auto;
box-sizing: border-box;
flex: 1 1 0;
min-width: 0;
min-height: 0;
&:not(:nth-last-child(1)) {
margin-right: var(--medium-padding);
@media (max-width: $mobile-width) {
// Was --small-padding (7.5px) too tight to read as a real gap
// between white tower cards on the cream-pink background.
margin-right: var(--medium-padding);
}
}
}
position: relative;
@for $i from 1 to 12 {
& > *: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) {
width: calc((100% - (#{$i} - 1) * var(--small-padding)) / #{$i});
}
}
}
// Mobile: fixed-width towers with horizontal scroll (1.5-column rhythm).
// This block is declared AFTER the @for loop so it wins the specificity race
// without needing !important on every property only the width triples need it
// to beat the per-child-count selectors generated above.
@media (max-width: $mobile-width) {
--mobile-tower-width: calc(66vw - var(--small-padding));
--mobile-tower-side-padding: max(
@ -109,17 +89,12 @@
display: none;
}
// Override the @for width-calc rules above.
& > * {
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-shrink: 0;
}
& > *:not(:nth-last-child(1)) {
margin-right: 0;
flex: 0 0 var(--mobile-tower-width);
}
}
}

View file

@ -27,12 +27,31 @@
[page]="selectedPage()"
(close)="showSettings.set(false)"
(updatePage)="onUpdatePage($event)"
(deletePage)="onRemovePage()"
(deletePage)="onRequestRemovePage()"
(switchAccount)="onSwitchAccount($event)"
/>
</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()) {
<lt-modal (close)="showWelcome.set(false)">
<lt-welcome

View file

@ -44,4 +44,65 @@
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();
}
}
p {
strong {
font-weight: bold;
}
}
.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%;
}
}
}
}
}

View file

@ -31,6 +31,7 @@ export class PagesComponent {
readonly showSettings = signal(false);
readonly dragHappening = signal(false);
readonly showWelcome = signal(false);
readonly confirmDeletePageId = signal<string | null>(null);
constructor() {
effect(() => {
@ -63,6 +64,12 @@ export class PagesComponent {
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(() => {
const page = this.selectedPage();
if (!page) return -1;
@ -94,12 +101,23 @@ export class PagesComponent {
}
}
onRemovePage(): void {
onRequestRemovePage(): void {
const page = this.selectedPage();
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.showSettings.set(false);
this.confirmDeletePageId.set(null);
}
cancelRemovePage(): void {
this.confirmDeletePageId.set(null);
}
onSwitchAccount(token: string): void {

View file

@ -28,6 +28,9 @@ import {
</div>
<div class="bottom-container">
<div class="bottom" [class.open]="open()">
@if (resolvedItems().length === 0 && emptyHint()) {
<p class="empty-hint">{{ emptyHint() }}</p>
}
@for (item of resolvedItems(); track item) {
@if (editing()) {
<input
@ -201,6 +204,14 @@ import {
}
}
.empty-hint {
@include medium-text();
width: 100%;
margin: 0;
color: rgba($text-color, 0.72);
text-align: left;
}
input[type='text'] {
@include sub-title-text();
width: 100%;
@ -373,6 +384,7 @@ export class SelectAddComponent implements OnChanges {
readonly selected = input<string | null>(null);
readonly editable = input<boolean>(false);
readonly placeholder = input<string>('Select…');
readonly emptyHint = input<string>('');
readonly alwaysDropShadow = input<boolean>(false);
readonly onlyShadowBorder = input<boolean>(false);