Groundwork for the continuous interface scale (a root font-size lever): every size the UI derives from must be root-relative, or it freezes while the rest grows. Gate for this commit: at the default 16px root the app renders pixel-identical to before — every conversion is exact (11px = 0.6875rem …). - The 8 panel-chrome tokens in styles.css: px -> rem, and the data-text compact/large tiers are DELETED — a continuous scale replaces discrete tiers. Same for --w7-fs (12px -> 0.75rem) and the win7 --radius (5px -> 0.3125rem, still landing rounded-md exactly on --w7-el-bdr's 3px). --radius-sm/md subtrahends: px -> rem, or corners would sharpen as the UI grew. - All 93 text-[11px]/text-[10px] arbitraries -> rem equivalents, plus the two inline padding:24px empty-state cells in the 7like file panel. - 7like.css gains a scale bridge in skinfix: 7.css is 370 px values with zero rem, and --w7-font reaches only 15 rules' TYPOGRAPHY — under a root lever win7 text would grow inside boxes frozen at 23px/14px/15px. The bridge re-expresses 7.css's size vars and its hardcoded box metrics (buttons via .w7-btn only, inputs, table rows/headers, title-bar controls, menu items, tabs, the 10pt check glyph) in rem at identical scale-1 pixels. 1px hairlines and focus rects stay px deliberately — crisp at any scale, and the tr border exists precisely to stop selection shifting rows. - Two skinfix traps found while writing it, one by reasoning and one by pixels: (a) NO blanket `.win7 button` restatement — skinfix outranks the skin-layer neutralizer that strips 7.css's 75x23 metrics off the ~84 icon buttons, so a blanket rule would re-inflate them all; (b) restating a base rule in skinfix also outranks 7.css's own VARIANTS of it by layer — the title-bar rule silently shrank the close button from 48px to 29px until the harness pixel diff caught it, and the active tab's padding-bottom needed the same treatment. Both variants are restated alongside their base rules. - TS px constants that mirror rem CSS are the latent-bug half of this work: both context menus' flip/clamp math converts through a new @core/metrics remPx() helper reading the LIVE root font-size (the shadlike menu's MENU_W=224 diverged from its w-56 box the moment scale != 1; the 7like menu's [style.width.px] pinned the box at 200px while text grew — it now binds width in rem, so math and box cannot disagree). The editors' cachedCharWidth is now keyed on the computed font string, self-invalidating under any font change, and both || px fallbacks derive from the element's font size instead of assuming a 16px root. Verified: tsc + build clean; modern and win7 live captures and the harness all diff to zero against pre-change baselines in their opaque regions (the harness to exactly zero end to end; modern's only delta is the native GTK titlebar focus state; win7's is the translucent Aero corner over the desktop). Co-Authored-By: Claude <noreply@anthropic.com>
82 lines
3.4 KiB
TypeScript
82 lines
3.4 KiB
TypeScript
import { Component, computed, inject } from '@angular/core';
|
|
import { NgIcon } from '@ng-icons/core';
|
|
import { I18nService } from '@core/i18n.service';
|
|
import type { TransferSnapshot } from '@shared/types';
|
|
import { formatSpeed } from '@core/format';
|
|
import { PanelsService } from '@core/panels.service';
|
|
import { SettingsService } from '@core/settings.service';
|
|
import { TransfersService } from '@core/transfers.service';
|
|
|
|
@Component({
|
|
selector: 'app-status-bar',
|
|
imports: [NgIcon],
|
|
template: `
|
|
<div
|
|
class="flex h-6 items-center justify-between gap-4 border-t border-border bg-chrome px-3 text-[0.6875rem] text-muted-foreground select-none"
|
|
>
|
|
<!-- transfers summary; click toggles the detailed list -->
|
|
<!-- w7-flat: this is a text toggle, not a push button. Without it 7.css
|
|
paints the full Aero slab and the status line reads as a stray
|
|
bordered button sitting in the status bar. -->
|
|
<button
|
|
(click)="transfers.expanded.set(!transfers.expanded())"
|
|
class="w7-flat flex min-w-0 items-center gap-1.5 hover:text-foreground"
|
|
[title]="i18n.t('transfer.showDetails')"
|
|
>
|
|
@if (transfers.currentJob(); as job) {
|
|
<ng-icon name="mynaSpinner" size="0.8rem" class="shrink-0 animate-spin text-primary" />
|
|
<span class="truncate">
|
|
{{ i18n.t('transfer.copying', { name: job.name, percent: percent(job), speed: speedSuffix(job) }) }}
|
|
@if (transfers.queuedCount(); as queued) {
|
|
· {{ i18n.t('transfer.queued', { count: queued }) }}
|
|
}
|
|
</span>
|
|
} @else if (transfers.failedJobs().length; as failed) {
|
|
<ng-icon name="mynaXCircle" size="0.8rem" class="shrink-0 text-destructive" />
|
|
<span class="text-destructive">
|
|
{{ i18n.t('transfer.failed', { count: failed }) }}
|
|
</span>
|
|
} @else if (transfers.hasJobs()) {
|
|
<ng-icon name="mynaCheckCircle" size="0.8rem" class="shrink-0 text-success" />
|
|
<span>{{ i18n.t('transfer.finished') }}</span>
|
|
} @else {
|
|
<span>{{ i18n.t('transfer.ready') }}</span>
|
|
}
|
|
</button>
|
|
|
|
<!-- active panel info -->
|
|
<div class="flex shrink-0 items-center gap-1.5">
|
|
<ng-icon
|
|
[name]="activePanel().sessionId() === 'local' ? 'mynaHardDrive' : 'mynaServers'"
|
|
size="0.8rem"
|
|
[class.text-primary]="activePanel().sessionId() !== 'local'"
|
|
/>
|
|
<span class="max-w-56 truncate">
|
|
{{ activePanel().sessionId() === 'local' ? i18n.t('common.local') : activePanel().title() }}
|
|
</span>
|
|
<span>· {{ i18n.t('transfer.items', { count: activePanel().sorted().length }) }}</span>
|
|
@if (activePanel().selected().size; as count) {
|
|
<span>· {{ i18n.t('transfer.selected', { count }) }}</span>
|
|
}
|
|
</div>
|
|
</div>
|
|
`,
|
|
})
|
|
export class StatusBar {
|
|
readonly i18n = inject(I18nService);
|
|
readonly transfers = inject(TransfersService);
|
|
readonly panels = inject(PanelsService);
|
|
readonly settings = inject(SettingsService);
|
|
readonly activePanel = computed(() => this.panels.get(this.panels.activeSide()));
|
|
|
|
percent(job: TransferSnapshot): number {
|
|
if (!job.totalBytes) return 0;
|
|
return Math.min(100, Math.round((job.doneBytes / job.totalBytes) * 100));
|
|
}
|
|
|
|
speedSuffix(job: TransferSnapshot): string {
|
|
const speed = this.transfers.speeds().get(job.id);
|
|
return speed ? ` · ${formatSpeed(speed, this.settings.speedUnit())}` : '';
|
|
}
|
|
}
|