From 5d23809d48709938dd582e0ea862eee16e1eacdd Mon Sep 17 00:00:00 2001 From: Jokin Date: Fri, 9 Oct 2026 13:24:38 +0200 Subject: [PATCH] Name every icon by the pixel grid it is drawn for Groundwork for redrawing the icons on their pixel grids. A name is now (server14, server16, server24) and must match the size token where it renders: 14 at --icon-size-sm, 16 at -md, 24 at -xl. tdesignLoading is the one exception, since it spins. Every name not yet redrawn aliases its TDesign glyph, so nothing changes on screen except the manager-open overlay icon, which moves from lg to xl ("a status mark on a card"). - pixel-icons.ts holds the redrawn glyphs, so far add14 (the + from 3f7145d, renamed from pixelAdd). - TS code that picks icons takes Icon14 / Icon16 (menu items, file glyphs, the copy arrow, toasts, transfer states), so build:ui rejects a wrong name. - icons.spec.ts checks every in the skins: it has a size token, and each literal name, both sides of a [name] ternary included, is registered and drawn for that token's grid. - TDesign's file1 is now fileText, since file1 plus a suffix would read file114. Co-Authored-By: Claude --- docs/architecture.md | 14 ++- docs/gotchas.md | 2 +- ui/src/app/core/context-menu.service.ts | 19 ++-- ui/src/app/core/file-icons.ts | 25 ++--- ui/src/app/core/icons.spec.ts | 33 +++++++ ui/src/app/core/icons.ts | 92 ++++++++++--------- ui/src/app/core/pixel-icons.ts | 16 ++++ ui/src/app/skins/shadlike/app.html | 4 +- .../connection-manager.html | 22 ++--- ui/src/app/skins/shadlike/context-menu.ts | 2 +- .../shadlike/dialogs/overwrite-dialog.ts | 2 +- .../shadlike/editor-window/editor-window.html | 18 ++-- .../skins/shadlike/file-panel/file-panel.html | 24 ++--- .../skins/shadlike/file-panel/file-panel.ts | 13 +-- .../settings-window/settings-window.html | 12 +-- .../skins/shadlike/status-bar/status-bar.ts | 6 +- ui/src/app/skins/shadlike/tab-bar/tab-bar.ts | 14 +-- ui/src/app/skins/shadlike/toasts/toasts.ts | 11 ++- .../shadlike/transfer-bar/transfer-bar.html | 6 +- .../shadlike/transfer-bar/transfer-bar.ts | 7 +- ui/src/styles.css | 4 +- 21 files changed, 208 insertions(+), 138 deletions(-) create mode 100644 ui/src/app/core/icons.spec.ts create mode 100644 ui/src/app/core/pixel-icons.ts diff --git a/docs/architecture.md b/docs/architecture.md index 801199c..ad8015d 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -372,11 +372,15 @@ component; tokens → `ui/src/styles.css`. walker check the flag themselves. - `file-icons.ts` — extension → category map driving glyphs, colors (fixed `--icon-*` tokens from Oxide's hue scales) and the Kind column. -- `icons.ts` — the one icon registry (TDesign, `@ng-icons/tdesign-icons`, plus one - redrawn glyph: `pixelAdd`, TDesign's + on the 14px pixel grid so it stays sharp - on 1x screens), passed to `provideIcons` in `app.config.ts`. Icon names are plain strings in templates and - the build never checks them: an unregistered name renders nothing (console error - only), so every name used anywhere must be listed here. +- `icons.ts` — the one icon registry, passed to `provideIcons` in `app.config.ts`. + A name is ``: the pixel grid its glyph is drawn for, which must match + the size token where it renders (`server14` at `--icon-size-sm`, `server16` at + `-md`, `server24` at `-xl`). The one exception is `tdesignLoading`, which spins. + `pixel-icons.ts` holds the glyphs redrawn on their grids, and names not yet + redrawn alias TDesign's (`@ng-icons/tdesign-icons`). Templates can't type-check + icon names, and an unregistered one renders nothing (a console warning only), so + `icons.spec.ts` checks every `` in the skins. TS code that picks an icon + takes the `Icon14` / `Icon16` types. - `platform.ts` — preload-global readers (`PLATFORM`, `APP_MODE`, `EDITOR_CONTEXT`, `IS_MAC`/`IS_MAC_CHROME`, `IS_WINDOWS`, `IS_LINUX`) + `blockNativeContextMenu` (kills WebKit's Reload/Inspect menu outside text diff --git a/docs/gotchas.md b/docs/gotchas.md index 9172509..9313458 100644 --- a/docs/gotchas.md +++ b/docs/gotchas.md @@ -86,7 +86,7 @@ touching the adjacent code. small size.** At 12 or 14px the centre is a pixel boundary, so a ~1px bar covers two pixels at half ink. A 1px stroke alone doesn't help, because the bar stays centred on the boundary. This is geometry, not Electrobun: - headless Chrome renders the same. So the + is `pixelAdd` (`icons.ts`), + headless Chrome renders the same. So the + is `add14` (`pixel-icons.ts`), redrawn on the 14px grid with 1px bars at 7.5, which fill whole pixels at 1x and at 2x. It's tuned for `--icon-size-sm` only. Arrow shafts (↑ →) have the same issue if it ever shows. diff --git a/ui/src/app/core/context-menu.service.ts b/ui/src/app/core/context-menu.service.ts index 3d46d7e..d076fe2 100644 --- a/ui/src/app/core/context-menu.service.ts +++ b/ui/src/app/core/context-menu.service.ts @@ -1,6 +1,7 @@ import { Injectable, inject, signal } from '@angular/core'; import type { ColumnKey, EntryType } from '@shared/types'; import type { FileCommand } from './file-actions.service'; +import type { Icon14 } from './icons'; import { I18nService } from './i18n.service'; import { stepIndex } from './menu-nav'; import type { Side } from './panels.service'; @@ -23,8 +24,8 @@ export interface MenuItem { /** The keymap command the item runs: its first key shows as the soft hint, * and pressing that key while the menu is open picks the item. */ shortcut?: ShortcutId; - /** A registered icon (core/icons.ts), drawn in the check column. */ - icon?: string; + /** A 14px icon (core/icons.ts), drawn in the check column. */ + icon?: Icon14; /** Greyed and unclickable. Menus disable rather than hide, so the set of * commands stays in the same place from one right-click to the next. */ disabled?: boolean; @@ -123,7 +124,7 @@ export class ContextMenuService { if (!ctx.hasEntry || ctx.name === '..') { // empty space (or the parent row): folder-level actions - items.push(cmd('new-folder', t('ctx.newFolder'), { icon: 'tdesignFolderAdd' })); + items.push(cmd('new-folder', t('ctx.newFolder'), { icon: 'folderAdd14' })); items.push(cmd('new-file', t('ctx.newFile'))); items.push({ type: 'separator' }); items.push(cmd('refresh', t('ctx.refresh'))); @@ -135,7 +136,7 @@ export class ContextMenuService { } // the arrow points at the other panel, as on the toolbar's Send button - const sendIcon = ctx.side === 'left' ? 'tdesignArrowRight' : 'tdesignArrowLeft'; + const sendIcon = ctx.side === 'left' ? 'arrowRight14' : 'arrowLeft14'; items.push(cmd('send', t('ctx.copyToOther'), { icon: sendIcon })); if (ctx.type === 'dir') { items.push(cmd('open', t('ctx.open'))); @@ -143,9 +144,9 @@ export class ContextMenuService { if (ctx.isLocal) { items.push(cmd('open-default', t('ctx.open'), { tooltip: t('ctx.openDefault') })); } - items.push(cmd('edit', t('ctx.edit'), { icon: 'tdesignFile1' })); + items.push(cmd('edit', t('ctx.edit'), { icon: 'fileText14' })); } - items.push(cmd('rename', t('ctx.rename'), { icon: 'tdesignEdit' })); + items.push(cmd('rename', t('ctx.rename'), { icon: 'edit14' })); // chmod on Windows local files only flips the read-only bit — hide the lie if (!(ctx.isLocal && IS_WINDOWS)) { items.push(cmd('chmod', t('ctx.permissions'))); @@ -159,7 +160,7 @@ export class ContextMenuService { if (ctx.isLocal) { items.push(cmd('trash', t(trashKey()), IS_MAC ? { tooltip: t('ctx.trashTooltip') } : {})); } - items.push(cmd('delete', t('ctx.delete'), { icon: 'tdesignDelete' })); + items.push(cmd('delete', t('ctx.delete'), { icon: 'delete14' })); return items; } @@ -180,13 +181,13 @@ export class ContextMenuService { const t = (key: string) => this.i18n.t(key); const last = ctx.tabCount <= 1; return [ - { label: t('ctx.tabNew'), action: 'tab:new', shortcut: 'new-tab', icon: 'pixelAdd' }, + { label: t('ctx.tabNew'), action: 'tab:new', shortcut: 'new-tab', icon: 'add14' }, { type: 'separator' }, { label: t('ctx.tabClose'), action: `tab:close:${ctx.id}`, shortcut: 'close-tab', - icon: 'tdesignClose', + icon: 'close14', disabled: last, }, { diff --git a/ui/src/app/core/file-icons.ts b/ui/src/app/core/file-icons.ts index 3895124..be50ea8 100644 --- a/ui/src/app/core/file-icons.ts +++ b/ui/src/app/core/file-icons.ts @@ -3,6 +3,7 @@ // Only the icon glyph is colored; row text stays neutral. Docs and unknown // files stay muted on purpose — sparing color is what keeps it scannable. import type { FileEntry } from '@shared/types'; +import type { Icon16 } from './icons'; type Category = 'image' | 'audio' | 'video' | 'archive' | 'code' | 'doc'; @@ -11,29 +12,29 @@ const MUTED = 'shrink-0 text-muted-foreground'; const FOLDER = 'shrink-0 text-icon-folder'; const LINK = 'shrink-0 text-icon-link'; -const CATEGORIES: Record = { +const CATEGORIES: Record = { image: { - glyph: 'tdesignImage', + glyph: 'image16', class: 'shrink-0 text-icon-image', extensions: 'png jpg jpeg gif webp avif svg ico icns bmp tiff tif heic heif raw'.split(' '), }, audio: { - glyph: 'tdesignMusic', + glyph: 'music16', class: 'shrink-0 text-icon-media', extensions: 'mp3 wav flac aac ogg oga m4a opus wma aiff aif mid midi'.split(' '), }, video: { - glyph: 'tdesignVideo', + glyph: 'video16', class: 'shrink-0 text-icon-media', extensions: 'mp4 mkv mov avi webm m4v mpg mpeg wmv flv 3gp'.split(' '), }, archive: { - glyph: 'tdesignFileZip', + glyph: 'fileZip16', class: 'shrink-0 text-icon-archive', extensions: 'zip tar gz tgz bz2 xz zst 7z rar dmg iso jar deb rpm pkg apk'.split(' '), }, code: { - glyph: 'tdesignCode', + glyph: 'code16', class: 'shrink-0 text-icon-code', // ".ts" is TypeScript here, not an MPEG transport stream (deliberate) extensions: ( @@ -45,7 +46,7 @@ const CATEGORIES: Record = { sm: '14', md: '16', lg: '20', xl: '24' }; +const SKINS = `${import.meta.dir}/../skins`; + +// Templates can't type-check icon names, so read them: every has a +// size token, and every literal name (both sides of a [name] ternary too) is +// registered and drawn for that token's grid. Names computed in TS are typed. +test('every template icon is registered and drawn for the size it renders at', async () => { + const problems: string[] = []; + for (const file of new Bun.Glob('**/*.{html,ts}').scanSync(SKINS)) { + const src = (await Bun.file(`${SKINS}/${file}`).text()).replace(//g, ''); + for (const [tag] of src.matchAll(/]*>/g)) { + const token = tag.match(/size="var\(--icon-size-(\w+)\)"/)?.[1]; + if (!token || !GRID[token]) { + problems.push(`${file}: no size token in ${tag}`); + continue; + } + const bound = tag.match(/\[name\]="([^"]*)"/)?.[1] ?? ''; + // a ternary's results, not its condition ('local' in `id === 'local' ? …`) + const names = [...tag.matchAll(/\bname="(\w+)"/g), ...bound.matchAll(/[?:]\s*'(\w+)'/g)].map((m) => m[1]); + for (const name of names) { + if (!Object.hasOwn(APP_ICONS, name)) problems.push(`${file}: ${name} is not registered`); + else if (name !== 'tdesignLoading' && !name.endsWith(GRID[token])) { + problems.push(`${file}: ${name} renders at ${token} (${GRID[token]}px)`); + } + } + } + } + expect(problems).toEqual([]); +}); diff --git a/ui/src/app/core/icons.ts b/ui/src/app/core/icons.ts index a84fa0e..bb282ce 100644 --- a/ui/src/app/core/icons.ts +++ b/ui/src/app/core/icons.ts @@ -1,7 +1,8 @@ // Single icon registry for the whole app, registered once in app.config.ts. -// TDesign's package root exports every icon; named imports tree-shake. Names are -// plain strings in templates — the build never checks them, so every name used -// anywhere must be listed here (an unregistered one renders nothing). +// A name is : the pixel grid the glyph is drawn for, which must match +// the size token where it renders (pixel-icons.ts). Templates can't type-check +// names and an unregistered one renders nothing (a console warning only), so +// icons.spec.ts checks every template name; TS code takes the Icon14/Icon16 types. import { tdesignArrowLeft, tdesignArrowRight, @@ -35,45 +36,54 @@ import { tdesignSetting, tdesignVideo, } from "@ng-icons/tdesign-icons"; - -// TDesign's + redrawn on the pixel grid of --icon-size-sm (14px). Its own glyph -// centres 24-grid strokes on the box's middle, a pixel boundary at 14px, so on a -// 1x screen both bars smear over two pixels at half ink. These bars are 1px wide -// on a half pixel (7.5): whole pixels at 1x and 2x, half a pixel off-centre. -const pixelAdd = - ''; +import { PIXEL_ICONS } from "./pixel-icons"; export const APP_ICONS = { - pixelAdd, - tdesignArrowLeft, - tdesignArrowRight, - tdesignArrowUp, - tdesignCheck, - tdesignCheckCircle, - tdesignChevronDown, - tdesignChevronRight, - tdesignChevronUp, - tdesignClose, - tdesignCloseCircle, - tdesignCode, - tdesignDelete, - tdesignDownload, - tdesignEdit, - tdesignFile, - tdesignFile1, - tdesignFileAdd, - tdesignFileZip, - tdesignFolder, - tdesignFolderAdd, - tdesignHardDrive, - tdesignImage, - tdesignLink, + ...PIXEL_ICONS, + // TDesign's glyphs under their grid names, until each is redrawn in pixel-icons.ts + arrowLeft14: tdesignArrowLeft, + arrowRight14: tdesignArrowRight, + check14: tdesignCheck, + checkCircle14: tdesignCheckCircle, + chevronDown14: tdesignChevronDown, + chevronRight14: tdesignChevronRight, + chevronUp14: tdesignChevronUp, + close14: tdesignClose, + closeCircle14: tdesignCloseCircle, + delete14: tdesignDelete, + download14: tdesignDownload, + edit14: tdesignEdit, + file14: tdesignFile, + fileAdd14: tdesignFileAdd, + fileText14: tdesignFile1, + folder14: tdesignFolder, + folderAdd14: tdesignFolderAdd, + hardDrive14: tdesignHardDrive, + lockOn14: tdesignLockOn, + refresh14: tdesignRefresh, + search14: tdesignSearch, + server14: tdesignServer, + setting14: tdesignSetting, + arrowUp16: tdesignArrowUp, + code16: tdesignCode, + file16: tdesignFile, + fileText16: tdesignFile1, + fileZip16: tdesignFileZip, + folder16: tdesignFolder, + image16: tdesignImage, + link16: tdesignLink, + music16: tdesignMusic, + refresh16: tdesignRefresh, + server16: tdesignServer, + setting16: tdesignSetting, + video16: tdesignVideo, + server24: tdesignServer, + // TDesign's own 24px grid: as sharp as a circle and its diagonals can be + closeCircle24: tdesignCloseCircle, + // spins, so no pixel grid holds; any size tdesignLoading, - tdesignLockOn, - tdesignMusic, - tdesignRefresh, - tdesignSearch, - tdesignServer, - tdesignSetting, - tdesignVideo, }; + +export type IconName = keyof typeof APP_ICONS; +export type Icon14 = Extract; +export type Icon16 = Extract; diff --git a/ui/src/app/core/pixel-icons.ts b/ui/src/app/core/pixel-icons.ts new file mode 100644 index 0000000..fdab2fe --- /dev/null +++ b/ui/src/app/core/pixel-icons.ts @@ -0,0 +1,16 @@ +// Icons drawn on the pixel grid they render at. A TDesign glyph is a 24-unit +// SVG with 2-unit strokes on whole units, so at 14 or 16px every straight line +// lands between pixels and smears over two at half ink on a 1x screen. Each +// glyph here is drawn on its own grid (viewBox 0 0 N N for an N px icon, one +// unit = one CSS pixel at 100% scale) with 1px strokes on half pixels, so +// straight lines fill whole pixels at 1x and 2x. The name's suffix is that +// grid and must match the size token where it renders: 14 = --icon-size-sm, +// 16 = -md, 24 = -xl (icons.spec.ts checks). Drawing rules: docs/gotchas.md. + +/** One glyph on an n-px grid: 1px square-capped strokes in the text colour. */ +const svg = (n: number, body: string) => + `${body}`; + +export const PIXEL_ICONS = { + add14: svg(14, ''), +}; diff --git a/ui/src/app/skins/shadlike/app.html b/ui/src/app/skins/shadlike/app.html index c893df7..dea5a31 100644 --- a/ui/src/app/skins/shadlike/app.html +++ b/ui/src/app/skins/shadlike/app.html @@ -2,7 +2,7 @@ @if (!rpc.available) {
- +

{{ i18n.t('app.notConnectedTitle') }}

{{ i18n.t('app.notConnectedBody') }} @@ -28,7 +28,7 @@ (mousedown)="focusManager()" >

- +

{{ i18n.t('app.managerOpen') }}

@@ -68,7 +68,7 @@ [title]="group.stack.collapsed ? i18n.t('connMgr.expand') : i18n.t('connMgr.collapse')" > @@ -90,14 +90,14 @@ [title]="shortcuts.tip(i18n.t('connMgr.renameStack'), 'conn-rename-stack')" class="grid size-4 place-items-center text-muted-foreground hover:text-foreground" > - + @if (group.connections.length) { }
@@ -123,7 +123,7 @@ [class.bg-accent]="draft()?.id === connection.id" [class.opacity-50]="draggedId() === connection.id" > - + {{ connection.name }} } @empty { @@ -161,7 +161,7 @@ [class.bg-accent]="draft()?.id === connection.id" [class.opacity-50]="draggedId() === connection.id" > - + {{ connection.name }} } @@ -342,7 +342,7 @@ [title]="shortcuts.tip(i18n.t('common.delete'), 'conn-delete')" class="flex items-center gap-1.5 px-2 py-1.5 text-xs text-muted-foreground hover:bg-accent hover:text-destructive" > - {{ i18n.t('common.delete') }} + {{ i18n.t('common.delete') }} @if (findOpen()) {
- + - +
} @@ -93,7 +93,7 @@
- + {{ i18n.t('editor.disconnected', { name: ctx.sessionTitle }) }}
} @@ -105,7 +105,7 @@ } @else if (loadError(); as error) {
- +

{{ i18n.t('editor.cantOpen', { name: ctx.name }) }}

{{ error }}

@if (chmodAvailable()) { } @@ -95,7 +95,7 @@ [title]="shortcuts.tip(i18n.t('panel.parentDir'), 'parent')" class="grid size-(--path-ctl-h) shrink-0 place-items-center text-muted-foreground hover:bg-accent hover:text-foreground disabled:opacity-50 disabled:hover:bg-transparent" > - + @@ -119,7 +119,7 @@ @if (panel().loading()) { } @else { - + }
@@ -153,7 +153,7 @@ @if (panel().filter(); as query) {
- + {{ query }} {{ i18n.t('panel.matches', { count: filterMatches() }) }} @@ -163,7 +163,7 @@ [title]="i18n.t('panel.clearFilter')" class="grid size-5 shrink-0 place-items-center text-muted-foreground hover:bg-accent hover:text-foreground" > - +
} diff --git a/ui/src/app/skins/shadlike/file-panel/file-panel.ts b/ui/src/app/skins/shadlike/file-panel/file-panel.ts index dcb0b78..4c97248 100644 --- a/ui/src/app/skins/shadlike/file-panel/file-panel.ts +++ b/ui/src/app/skins/shadlike/file-panel/file-panel.ts @@ -18,6 +18,7 @@ import { DialogsService } from '@core/dialogs.service'; import { FileActionsService, type FileCommand, type FileTarget } from '@core/file-actions.service'; import { fileGlyph, fileIconClass, fileKindKey } from '@core/file-icons'; import { formatMode } from '@core/format'; +import type { Icon14, Icon16 } from '@core/icons'; import { remPx } from '@core/metrics'; import { PanelsService, type Side, type SortKey } from '@core/panels.service'; import { IS_WINDOWS } from '@core/platform'; @@ -91,7 +92,7 @@ export class FilePanel { /** Input is ignored while the panel is connecting or a listing is in flight. */ readonly blocked = computed(() => this.panel().busy()); /** The copy button points toward the other panel. */ - readonly copyIcon = computed(() => (this.side() === 'left' ? 'tdesignArrowRight' : 'tdesignArrowLeft')); + readonly copyIcon = computed(() => (this.side() === 'left' ? 'arrowRight14' : 'arrowLeft14')); readonly pathDraft = signal(''); /** Windows drive roots for the source picker; always empty elsewhere. */ @@ -192,20 +193,20 @@ export class FilePanel { { label: this.i18n.t('common.local'), action: 'local', - icon: 'tdesignHardDrive', + icon: 'hardDrive14', checked: !this.isRemote(), }, ]; // Windows drive roots (empty list elsewhere) for (const drive of this.drives()) { - items.push({ label: drive, action: `drive:${drive}`, icon: 'tdesignHardDrive' }); + items.push({ label: drive, action: `drive:${drive}`, icon: 'hardDrive14' }); } const { loose, stacks } = this.connections.grouped(); if (loose.length || stacks.length) items.push({ type: 'separator' }); const server = (connection: { id: string; name: string }): MenuItem => ({ label: connection.name, action: `conn:${connection.id}`, - icon: 'tdesignServer', + icon: 'server14', }); items.push(...loose.map(server)); for (const group of stacks) { @@ -218,7 +219,7 @@ export class FilePanel { items.push({ label: host.name, action: `ssh:${host.name}`, - icon: 'tdesignServer', + icon: 'server14', disabled: !!host.unsupported, tooltip: this.sshHostTitle(host), }); @@ -267,7 +268,7 @@ export class FilePanel { return `${host.username}@${host.hostname}:${host.port}`; } - iconFor(entry: FileEntry): string { + iconFor(entry: FileEntry): Icon16 { return fileGlyph(entry); } diff --git a/ui/src/app/skins/shadlike/settings-window/settings-window.html b/ui/src/app/skins/shadlike/settings-window/settings-window.html index 8736019..548d234 100644 --- a/ui/src/app/skins/shadlike/settings-window/settings-window.html +++ b/ui/src/app/skins/shadlike/settings-window/settings-window.html @@ -9,7 +9,7 @@ [class.h-9]="!isMacChrome" [class.pl-3]="!isMacChrome" > - +

{{ i18n.t('options.title') }}

@@ -250,7 +250,7 @@ [title]="i18n.t('options.resetDefault')" class="grid size-5 place-items-center text-muted-foreground hover:bg-accent hover:text-foreground" > - + }
@@ -305,7 +305,7 @@
@@ -367,7 +367,7 @@ [title]="i18n.t('keys.ui.remove', { key: shortcuts.format(combo) })" class="grid size-4 place-items-center text-muted-foreground hover:text-foreground" > - + } @@ -387,7 +387,7 @@ class="grid size-5 shrink-0 place-items-center text-muted-foreground hover:bg-accent hover:text-foreground" [class.text-primary]="capturing() === binding.id" > - +
@if (note()?.id === binding.id) { diff --git a/ui/src/app/skins/shadlike/status-bar/status-bar.ts b/ui/src/app/skins/shadlike/status-bar/status-bar.ts index de32122..ce33b40 100644 --- a/ui/src/app/skins/shadlike/status-bar/status-bar.ts +++ b/ui/src/app/skins/shadlike/status-bar/status-bar.ts @@ -30,12 +30,12 @@ import { TransfersService } from '@core/transfers.service'; } } @else if (transfers.failedJobs().length; as failed) { - + {{ i18n.t('transfer.failed', { count: failed }) }} } @else if (transfers.hasJobs()) { - + {{ i18n.t('transfer.finished') }} } @else { {{ i18n.t('transfer.ready') }} @@ -45,7 +45,7 @@ import { TransfersService } from '@core/transfers.service';
diff --git a/ui/src/app/skins/shadlike/tab-bar/tab-bar.ts b/ui/src/app/skins/shadlike/tab-bar/tab-bar.ts index 2e9a0b7..104494c 100644 --- a/ui/src/app/skins/shadlike/tab-bar/tab-bar.ts +++ b/ui/src/app/skins/shadlike/tab-bar/tab-bar.ts @@ -52,7 +52,7 @@ import { UpdaterService } from '@core/updater.service'; [title]="panels.workspaceName(ws)" > - + } @else { @@ -83,7 +83,7 @@ import { UpdaterService } from '@core/updater.service'; [title]="shortcuts.tip(i18n.t('tabBar.newTab'), 'new-tab')" class="ml-0.5 grid size-6 place-items-center text-muted-foreground electrobun-webkit-app-region-no-drag hover:bg-accent hover:text-foreground" > - +
@@ -106,13 +106,13 @@ import { UpdaterService } from '@core/updater.service'; @switch (updater.state()) { @case ('available') { - {{ i18n.t('tabBar.update') }} + {{ i18n.t('tabBar.update') }} } @case ('downloading') { {{ i18n.t('tabBar.updating') }} {{ updater.progress() }}% } @case ('ready') { - {{ i18n.t('tabBar.restartToUpdate') }} + {{ i18n.t('tabBar.restartToUpdate') }} } } @@ -125,7 +125,7 @@ import { UpdaterService } from '@core/updater.service'; [title]="shortcuts.tip(i18n.t('tabBar.manageConnections'), 'connection-manager')" class="grid h-6 px-2 py-1 place-items-center text-[0.6875rem] text-muted-foreground electrobun-webkit-app-region-no-drag hover:bg-accent hover:text-foreground" > - + {{ i18n.t('tabBar.connections') }} @@ -135,7 +135,7 @@ import { UpdaterService } from '@core/updater.service'; [title]="shortcuts.tip(i18n.t('tabBar.options'), 'open-settings')" class="grid size-6 place-items-center text-muted-foreground electrobun-webkit-app-region-no-drag hover:bg-accent hover:text-foreground" > - +
`, diff --git a/ui/src/app/skins/shadlike/toasts/toasts.ts b/ui/src/app/skins/shadlike/toasts/toasts.ts index c465f39..2edea38 100644 --- a/ui/src/app/skins/shadlike/toasts/toasts.ts +++ b/ui/src/app/skins/shadlike/toasts/toasts.ts @@ -1,6 +1,7 @@ import { Component, inject } from '@angular/core'; import { NgIcon } from '@ng-icons/core'; import { I18nService } from '@core/i18n.service'; +import type { Icon14 } from '@core/icons'; import { ToastService } from '@core/toast.service'; /** Toast stack overlay; shared by the main window and editor windows. */ @@ -26,7 +27,7 @@ import { ToastService } from '@core/toast.service'; [attr.aria-label]="i18n.t('transfer.dismiss')" class="shrink-0 text-muted-foreground hover:text-foreground" > - +
- {{ i18n.t('transfer.clearFinished') }} + {{ i18n.t('transfer.clearFinished') }}
@@ -40,7 +40,7 @@ [title]="i18n.t('transfer.cancel')" class="shrink-0 p-1 text-muted-foreground hover:bg-accent hover:text-destructive" > - + } @else { }
diff --git a/ui/src/app/skins/shadlike/transfer-bar/transfer-bar.ts b/ui/src/app/skins/shadlike/transfer-bar/transfer-bar.ts index 01eb8f5..0642650 100644 --- a/ui/src/app/skins/shadlike/transfer-bar/transfer-bar.ts +++ b/ui/src/app/skins/shadlike/transfer-bar/transfer-bar.ts @@ -4,6 +4,7 @@ import type { TransferSnapshot } from '@shared/types'; import { cycleFocus } from '@core/focus-cycle'; import { formatEta } from '@core/format'; import { I18nService } from '@core/i18n.service'; +import type { Icon14 } from '@core/icons'; import { PanelsService } from '@core/panels.service'; import { SettingsService } from '@core/settings.service'; import { TransfersService } from '@core/transfers.service'; @@ -56,13 +57,13 @@ export class TransferBar { return parts.join(' · '); } - stateIcon(job: TransferSnapshot): string { + stateIcon(job: TransferSnapshot): Icon14 | 'tdesignLoading' { switch (job.state) { case 'completed': - return 'tdesignCheckCircle'; + return 'checkCircle14'; case 'failed': case 'cancelled': - return 'tdesignCloseCircle'; + return 'closeCircle14'; default: return 'tdesignLoading'; } diff --git a/ui/src/styles.css b/ui/src/styles.css index 3891301..013267b 100644 --- a/ui/src/styles.css +++ b/ui/src/styles.css @@ -204,7 +204,9 @@ the text beside it, in whole pixels (small button: 12px text, 14px icon). Every takes one of these four: a one-off size like 0.9rem (14.4px) puts every stroke between pixels, and one-offs drift until neighbours never - match. Rem like the rest, so the interface scale moves them too. */ + match. Rem like the rest, so the interface scale moves them too. + sm, md and xl are also the pixel grids the glyphs in core/pixel-icons.ts + are drawn on (14, 16, 24): change one and those glyphs blur until redrawn. */ :root { --icon-size-sm: 0.875rem; /* 14px: beside 11-12px text, and on small icon buttons */ --icon-size-md: 1rem; /* 16px: beside 13-14px text (file rows, window titles), path-bar buttons */