Name every icon by the pixel grid it is drawn for
Groundwork for redrawing the icons on their pixel grids. A name is now
<icon><px> (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 <ng-icon> 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 <noreply@anthropic.com>
This commit is contained in:
@@ -372,11 +372,15 @@ component; tokens → `ui/src/styles.css`.
|
|||||||
walker check the flag themselves.
|
walker check the flag themselves.
|
||||||
- `file-icons.ts` — extension → category map driving glyphs, colors
|
- `file-icons.ts` — extension → category map driving glyphs, colors
|
||||||
(fixed `--icon-*` tokens from Oxide's hue scales) and the Kind column.
|
(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
|
- `icons.ts` — the one icon registry, passed to `provideIcons` in `app.config.ts`.
|
||||||
redrawn glyph: `pixelAdd`, TDesign's + on the 14px pixel grid so it stays sharp
|
A name is `<icon><px>`: the pixel grid its glyph is drawn for, which must match
|
||||||
on 1x screens), passed to `provideIcons` in `app.config.ts`. Icon names are plain strings in templates and
|
the size token where it renders (`server14` at `--icon-size-sm`, `server16` at
|
||||||
the build never checks them: an unregistered name renders nothing (console error
|
`-md`, `server24` at `-xl`). The one exception is `tdesignLoading`, which spins.
|
||||||
only), so every name used anywhere must be listed here.
|
`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 `<ng-icon>` in the skins. TS code that picks an icon
|
||||||
|
takes the `Icon14` / `Icon16` types.
|
||||||
- `platform.ts` — preload-global readers (`PLATFORM`, `APP_MODE`,
|
- `platform.ts` — preload-global readers (`PLATFORM`, `APP_MODE`,
|
||||||
`EDITOR_CONTEXT`, `IS_MAC`/`IS_MAC_CHROME`, `IS_WINDOWS`, `IS_LINUX`) +
|
`EDITOR_CONTEXT`, `IS_MAC`/`IS_MAC_CHROME`, `IS_WINDOWS`, `IS_LINUX`) +
|
||||||
`blockNativeContextMenu` (kills WebKit's Reload/Inspect menu outside text
|
`blockNativeContextMenu` (kills WebKit's Reload/Inspect menu outside text
|
||||||
|
|||||||
+1
-1
@@ -86,7 +86,7 @@ touching the adjacent code.
|
|||||||
small size.** At 12 or 14px the centre is a pixel boundary, so a ~1px bar
|
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
|
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:
|
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
|
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
|
and at 2x. It's tuned for `--icon-size-sm` only. Arrow shafts (↑ →) have
|
||||||
the same issue if it ever shows.
|
the same issue if it ever shows.
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { Injectable, inject, signal } from '@angular/core';
|
import { Injectable, inject, signal } from '@angular/core';
|
||||||
import type { ColumnKey, EntryType } from '@shared/types';
|
import type { ColumnKey, EntryType } from '@shared/types';
|
||||||
import type { FileCommand } from './file-actions.service';
|
import type { FileCommand } from './file-actions.service';
|
||||||
|
import type { Icon14 } from './icons';
|
||||||
import { I18nService } from './i18n.service';
|
import { I18nService } from './i18n.service';
|
||||||
import { stepIndex } from './menu-nav';
|
import { stepIndex } from './menu-nav';
|
||||||
import type { Side } from './panels.service';
|
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,
|
/** 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. */
|
* and pressing that key while the menu is open picks the item. */
|
||||||
shortcut?: ShortcutId;
|
shortcut?: ShortcutId;
|
||||||
/** A registered icon (core/icons.ts), drawn in the check column. */
|
/** A 14px icon (core/icons.ts), drawn in the check column. */
|
||||||
icon?: string;
|
icon?: Icon14;
|
||||||
/** Greyed and unclickable. Menus disable rather than hide, so the set of
|
/** 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. */
|
* commands stays in the same place from one right-click to the next. */
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
@@ -123,7 +124,7 @@ export class ContextMenuService {
|
|||||||
|
|
||||||
if (!ctx.hasEntry || ctx.name === '..') {
|
if (!ctx.hasEntry || ctx.name === '..') {
|
||||||
// empty space (or the parent row): folder-level actions
|
// 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(cmd('new-file', t('ctx.newFile')));
|
||||||
items.push({ type: 'separator' });
|
items.push({ type: 'separator' });
|
||||||
items.push(cmd('refresh', t('ctx.refresh')));
|
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
|
// 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 }));
|
items.push(cmd('send', t('ctx.copyToOther'), { icon: sendIcon }));
|
||||||
if (ctx.type === 'dir') {
|
if (ctx.type === 'dir') {
|
||||||
items.push(cmd('open', t('ctx.open')));
|
items.push(cmd('open', t('ctx.open')));
|
||||||
@@ -143,9 +144,9 @@ export class ContextMenuService {
|
|||||||
if (ctx.isLocal) {
|
if (ctx.isLocal) {
|
||||||
items.push(cmd('open-default', t('ctx.open'), { tooltip: t('ctx.openDefault') }));
|
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
|
// chmod on Windows local files only flips the read-only bit — hide the lie
|
||||||
if (!(ctx.isLocal && IS_WINDOWS)) {
|
if (!(ctx.isLocal && IS_WINDOWS)) {
|
||||||
items.push(cmd('chmod', t('ctx.permissions')));
|
items.push(cmd('chmod', t('ctx.permissions')));
|
||||||
@@ -159,7 +160,7 @@ export class ContextMenuService {
|
|||||||
if (ctx.isLocal) {
|
if (ctx.isLocal) {
|
||||||
items.push(cmd('trash', t(trashKey()), IS_MAC ? { tooltip: t('ctx.trashTooltip') } : {}));
|
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;
|
return items;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -180,13 +181,13 @@ export class ContextMenuService {
|
|||||||
const t = (key: string) => this.i18n.t(key);
|
const t = (key: string) => this.i18n.t(key);
|
||||||
const last = ctx.tabCount <= 1;
|
const last = ctx.tabCount <= 1;
|
||||||
return [
|
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' },
|
{ type: 'separator' },
|
||||||
{
|
{
|
||||||
label: t('ctx.tabClose'),
|
label: t('ctx.tabClose'),
|
||||||
action: `tab:close:${ctx.id}`,
|
action: `tab:close:${ctx.id}`,
|
||||||
shortcut: 'close-tab',
|
shortcut: 'close-tab',
|
||||||
icon: 'tdesignClose',
|
icon: 'close14',
|
||||||
disabled: last,
|
disabled: last,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
// Only the icon glyph is colored; row text stays neutral. Docs and unknown
|
// 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.
|
// files stay muted on purpose — sparing color is what keeps it scannable.
|
||||||
import type { FileEntry } from '@shared/types';
|
import type { FileEntry } from '@shared/types';
|
||||||
|
import type { Icon16 } from './icons';
|
||||||
|
|
||||||
type Category = 'image' | 'audio' | 'video' | 'archive' | 'code' | 'doc';
|
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 FOLDER = 'shrink-0 text-icon-folder';
|
||||||
const LINK = 'shrink-0 text-icon-link';
|
const LINK = 'shrink-0 text-icon-link';
|
||||||
|
|
||||||
const CATEGORIES: Record<Category, { glyph: string; class: string; extensions: string[] }> = {
|
const CATEGORIES: Record<Category, { glyph: Icon16; class: string; extensions: string[] }> = {
|
||||||
image: {
|
image: {
|
||||||
glyph: 'tdesignImage',
|
glyph: 'image16',
|
||||||
class: 'shrink-0 text-icon-image',
|
class: 'shrink-0 text-icon-image',
|
||||||
extensions: 'png jpg jpeg gif webp avif svg ico icns bmp tiff tif heic heif raw'.split(' '),
|
extensions: 'png jpg jpeg gif webp avif svg ico icns bmp tiff tif heic heif raw'.split(' '),
|
||||||
},
|
},
|
||||||
audio: {
|
audio: {
|
||||||
glyph: 'tdesignMusic',
|
glyph: 'music16',
|
||||||
class: 'shrink-0 text-icon-media',
|
class: 'shrink-0 text-icon-media',
|
||||||
extensions: 'mp3 wav flac aac ogg oga m4a opus wma aiff aif mid midi'.split(' '),
|
extensions: 'mp3 wav flac aac ogg oga m4a opus wma aiff aif mid midi'.split(' '),
|
||||||
},
|
},
|
||||||
video: {
|
video: {
|
||||||
glyph: 'tdesignVideo',
|
glyph: 'video16',
|
||||||
class: 'shrink-0 text-icon-media',
|
class: 'shrink-0 text-icon-media',
|
||||||
extensions: 'mp4 mkv mov avi webm m4v mpg mpeg wmv flv 3gp'.split(' '),
|
extensions: 'mp4 mkv mov avi webm m4v mpg mpeg wmv flv 3gp'.split(' '),
|
||||||
},
|
},
|
||||||
archive: {
|
archive: {
|
||||||
glyph: 'tdesignFileZip',
|
glyph: 'fileZip16',
|
||||||
class: 'shrink-0 text-icon-archive',
|
class: 'shrink-0 text-icon-archive',
|
||||||
extensions: 'zip tar gz tgz bz2 xz zst 7z rar dmg iso jar deb rpm pkg apk'.split(' '),
|
extensions: 'zip tar gz tgz bz2 xz zst 7z rar dmg iso jar deb rpm pkg apk'.split(' '),
|
||||||
},
|
},
|
||||||
code: {
|
code: {
|
||||||
glyph: 'tdesignCode',
|
glyph: 'code16',
|
||||||
class: 'shrink-0 text-icon-code',
|
class: 'shrink-0 text-icon-code',
|
||||||
// ".ts" is TypeScript here, not an MPEG transport stream (deliberate)
|
// ".ts" is TypeScript here, not an MPEG transport stream (deliberate)
|
||||||
extensions: (
|
extensions: (
|
||||||
@@ -45,7 +46,7 @@ const CATEGORIES: Record<Category, { glyph: string; class: string; extensions: s
|
|||||||
).split(' '),
|
).split(' '),
|
||||||
},
|
},
|
||||||
doc: {
|
doc: {
|
||||||
glyph: 'tdesignFile1',
|
glyph: 'fileText16',
|
||||||
class: MUTED, // docs keep the neutral color; only the glyph changes
|
class: MUTED, // docs keep the neutral color; only the glyph changes
|
||||||
extensions: 'md markdown txt rtf pdf doc docx xls xlsx ppt pptx csv tsv log odt ods odp epub'.split(' '),
|
extensions: 'md markdown txt rtf pdf doc docx xls xlsx ppt pptx csv tsv log odt ods odp epub'.split(' '),
|
||||||
},
|
},
|
||||||
@@ -84,12 +85,12 @@ export function fileKindKey(entry: FileEntry): string {
|
|||||||
return category ? KIND_KEYS[category] : 'file';
|
return category ? KIND_KEYS[category] : 'file';
|
||||||
}
|
}
|
||||||
|
|
||||||
export function fileGlyph(entry: FileEntry): string {
|
export function fileGlyph(entry: FileEntry): Icon16 {
|
||||||
if (entry.name === '..') return 'tdesignArrowUp';
|
if (entry.name === '..') return 'arrowUp16';
|
||||||
if (entry.type === 'dir') return 'tdesignFolder';
|
if (entry.type === 'dir') return 'folder16';
|
||||||
if (entry.type === 'symlink') return 'tdesignLink';
|
if (entry.type === 'symlink') return 'link16';
|
||||||
const category = categoryOf(entry.name);
|
const category = categoryOf(entry.name);
|
||||||
return category ? CATEGORIES[category].glyph : 'tdesignFile';
|
return category ? CATEGORIES[category].glyph : 'file16';
|
||||||
}
|
}
|
||||||
|
|
||||||
export function fileIconClass(entry: FileEntry): string {
|
export function fileIconClass(entry: FileEntry): string {
|
||||||
|
|||||||
@@ -0,0 +1,33 @@
|
|||||||
|
import { expect, test } from 'bun:test';
|
||||||
|
import { APP_ICONS } from './icons';
|
||||||
|
|
||||||
|
// The px grid of each size token (styles.css); lg only carries the spinner.
|
||||||
|
const GRID: Record<string, string> = { 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 <ng-icon> 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(/<!--[\s\S]*?-->/g, '');
|
||||||
|
for (const [tag] of src.matchAll(/<ng-icon\b[^>]*>/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([]);
|
||||||
|
});
|
||||||
+51
-41
@@ -1,7 +1,8 @@
|
|||||||
// Single icon registry for the whole app, registered once in app.config.ts.
|
// 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
|
// A name is <icon><px>: the pixel grid the glyph is drawn for, which must match
|
||||||
// plain strings in templates — the build never checks them, so every name used
|
// the size token where it renders (pixel-icons.ts). Templates can't type-check
|
||||||
// anywhere must be listed here (an unregistered one renders nothing).
|
// 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 {
|
import {
|
||||||
tdesignArrowLeft,
|
tdesignArrowLeft,
|
||||||
tdesignArrowRight,
|
tdesignArrowRight,
|
||||||
@@ -35,45 +36,54 @@ import {
|
|||||||
tdesignSetting,
|
tdesignSetting,
|
||||||
tdesignVideo,
|
tdesignVideo,
|
||||||
} from "@ng-icons/tdesign-icons";
|
} from "@ng-icons/tdesign-icons";
|
||||||
|
import { PIXEL_ICONS } from "./pixel-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 =
|
|
||||||
'<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 14 14"><path stroke="currentColor" d="M7.5 3v9M3 7.5h9"/></svg>';
|
|
||||||
|
|
||||||
export const APP_ICONS = {
|
export const APP_ICONS = {
|
||||||
pixelAdd,
|
...PIXEL_ICONS,
|
||||||
tdesignArrowLeft,
|
// TDesign's glyphs under their grid names, until each is redrawn in pixel-icons.ts
|
||||||
tdesignArrowRight,
|
arrowLeft14: tdesignArrowLeft,
|
||||||
tdesignArrowUp,
|
arrowRight14: tdesignArrowRight,
|
||||||
tdesignCheck,
|
check14: tdesignCheck,
|
||||||
tdesignCheckCircle,
|
checkCircle14: tdesignCheckCircle,
|
||||||
tdesignChevronDown,
|
chevronDown14: tdesignChevronDown,
|
||||||
tdesignChevronRight,
|
chevronRight14: tdesignChevronRight,
|
||||||
tdesignChevronUp,
|
chevronUp14: tdesignChevronUp,
|
||||||
tdesignClose,
|
close14: tdesignClose,
|
||||||
tdesignCloseCircle,
|
closeCircle14: tdesignCloseCircle,
|
||||||
tdesignCode,
|
delete14: tdesignDelete,
|
||||||
tdesignDelete,
|
download14: tdesignDownload,
|
||||||
tdesignDownload,
|
edit14: tdesignEdit,
|
||||||
tdesignEdit,
|
file14: tdesignFile,
|
||||||
tdesignFile,
|
fileAdd14: tdesignFileAdd,
|
||||||
tdesignFile1,
|
fileText14: tdesignFile1,
|
||||||
tdesignFileAdd,
|
folder14: tdesignFolder,
|
||||||
tdesignFileZip,
|
folderAdd14: tdesignFolderAdd,
|
||||||
tdesignFolder,
|
hardDrive14: tdesignHardDrive,
|
||||||
tdesignFolderAdd,
|
lockOn14: tdesignLockOn,
|
||||||
tdesignHardDrive,
|
refresh14: tdesignRefresh,
|
||||||
tdesignImage,
|
search14: tdesignSearch,
|
||||||
tdesignLink,
|
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,
|
tdesignLoading,
|
||||||
tdesignLockOn,
|
|
||||||
tdesignMusic,
|
|
||||||
tdesignRefresh,
|
|
||||||
tdesignSearch,
|
|
||||||
tdesignServer,
|
|
||||||
tdesignSetting,
|
|
||||||
tdesignVideo,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type IconName = keyof typeof APP_ICONS;
|
||||||
|
export type Icon14 = Extract<IconName, `${string}14`>;
|
||||||
|
export type Icon16 = Extract<IconName, `${string}16`>;
|
||||||
|
|||||||
@@ -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) =>
|
||||||
|
`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${n} ${n}" fill="none" stroke="currentColor" stroke-linecap="square">${body}</svg>`;
|
||||||
|
|
||||||
|
export const PIXEL_ICONS = {
|
||||||
|
add14: svg(14, '<path d="M7.5 3.5v8M3.5 7.5h8"/>'),
|
||||||
|
};
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
@if (!rpc.available) {
|
@if (!rpc.available) {
|
||||||
<div class="flex flex-1 items-center justify-center">
|
<div class="flex flex-1 items-center justify-center">
|
||||||
<div class="max-w-sm border border-border bg-card p-6 text-center shadow-sm">
|
<div class="max-w-sm border border-border bg-card p-6 text-center shadow-sm">
|
||||||
<ng-icon name="tdesignCloseCircle" size="var(--icon-size-xl)" class="text-destructive" />
|
<ng-icon name="closeCircle24" size="var(--icon-size-xl)" class="text-destructive" />
|
||||||
<p class="mt-2 text-sm font-medium">{{ i18n.t('app.notConnectedTitle') }}</p>
|
<p class="mt-2 text-sm font-medium">{{ i18n.t('app.notConnectedTitle') }}</p>
|
||||||
<p class="mt-1 text-xs text-muted-foreground">
|
<p class="mt-1 text-xs text-muted-foreground">
|
||||||
{{ i18n.t('app.notConnectedBody') }}
|
{{ i18n.t('app.notConnectedBody') }}
|
||||||
@@ -28,7 +28,7 @@
|
|||||||
(mousedown)="focusManager()"
|
(mousedown)="focusManager()"
|
||||||
>
|
>
|
||||||
<div class="border border-border bg-card px-5 py-4 text-center shadow-xl">
|
<div class="border border-border bg-card px-5 py-4 text-center shadow-xl">
|
||||||
<ng-icon name="tdesignServer" size="var(--icon-size-lg)" class="text-muted-foreground" />
|
<ng-icon name="server24" size="var(--icon-size-xl)" class="text-muted-foreground" />
|
||||||
<p class="mt-1.5 text-xs text-muted-foreground">{{ i18n.t('app.managerOpen') }}</p>
|
<p class="mt-1.5 text-xs text-muted-foreground">{{ i18n.t('app.managerOpen') }}</p>
|
||||||
<button
|
<button
|
||||||
(click)="focusManager()"
|
(click)="focusManager()"
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
[class.h-9]="!isMacChrome"
|
[class.h-9]="!isMacChrome"
|
||||||
[class.pl-3]="!isMacChrome"
|
[class.pl-3]="!isMacChrome"
|
||||||
>
|
>
|
||||||
<ng-icon name="tdesignServer" size="var(--icon-size-md)" class="shrink-0 text-muted-foreground" />
|
<ng-icon name="server16" size="var(--icon-size-md)" class="shrink-0 text-muted-foreground" />
|
||||||
<h1 class="text-sm font-semibold">{{ i18n.t('connMgr.title') }}</h1>
|
<h1 class="text-sm font-semibold">{{ i18n.t('connMgr.title') }}</h1>
|
||||||
<div class="flex-1"></div>
|
<div class="flex-1"></div>
|
||||||
<button
|
<button
|
||||||
@@ -36,14 +36,14 @@
|
|||||||
[title]="shortcuts.tip(i18n.t('connMgr.newConnectionHint'), 'conn-new')"
|
[title]="shortcuts.tip(i18n.t('connMgr.newConnectionHint'), 'conn-new')"
|
||||||
class="flex items-center gap-1 px-2 py-1 text-xs text-muted-foreground hover:bg-accent hover:text-foreground"
|
class="flex items-center gap-1 px-2 py-1 text-xs text-muted-foreground hover:bg-accent hover:text-foreground"
|
||||||
>
|
>
|
||||||
<ng-icon name="pixelAdd" size="var(--icon-size-sm)" /> {{ i18n.t('connMgr.newConnection') }}
|
<ng-icon name="add14" size="var(--icon-size-sm)" /> {{ i18n.t('connMgr.newConnection') }}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
(click)="newStack()"
|
(click)="newStack()"
|
||||||
[title]="shortcuts.tip(i18n.t('connMgr.newStackHint'), 'conn-new-stack')"
|
[title]="shortcuts.tip(i18n.t('connMgr.newStackHint'), 'conn-new-stack')"
|
||||||
class="flex items-center gap-1 px-2 py-1 text-xs text-muted-foreground hover:bg-accent hover:text-foreground"
|
class="flex items-center gap-1 px-2 py-1 text-xs text-muted-foreground hover:bg-accent hover:text-foreground"
|
||||||
>
|
>
|
||||||
<ng-icon name="tdesignFolderAdd" size="var(--icon-size-sm)" /> {{ i18n.t('connMgr.newStack') }}
|
<ng-icon name="folderAdd14" size="var(--icon-size-sm)" /> {{ i18n.t('connMgr.newStack') }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -68,7 +68,7 @@
|
|||||||
[title]="group.stack.collapsed ? i18n.t('connMgr.expand') : i18n.t('connMgr.collapse')"
|
[title]="group.stack.collapsed ? i18n.t('connMgr.expand') : i18n.t('connMgr.collapse')"
|
||||||
>
|
>
|
||||||
<ng-icon
|
<ng-icon
|
||||||
[name]="group.stack.collapsed ? 'tdesignChevronRight' : 'tdesignChevronDown'"
|
[name]="group.stack.collapsed ? 'chevronRight14' : 'chevronDown14'"
|
||||||
size="var(--icon-size-sm)"
|
size="var(--icon-size-sm)"
|
||||||
/>
|
/>
|
||||||
</button>
|
</button>
|
||||||
@@ -90,14 +90,14 @@
|
|||||||
[title]="shortcuts.tip(i18n.t('connMgr.renameStack'), 'conn-rename-stack')"
|
[title]="shortcuts.tip(i18n.t('connMgr.renameStack'), 'conn-rename-stack')"
|
||||||
class="grid size-4 place-items-center text-muted-foreground hover:text-foreground"
|
class="grid size-4 place-items-center text-muted-foreground hover:text-foreground"
|
||||||
>
|
>
|
||||||
<ng-icon name="tdesignEdit" size="var(--icon-size-sm)" />
|
<ng-icon name="edit14" size="var(--icon-size-sm)" />
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
(click)="deleteStack(group.stack)"
|
(click)="deleteStack(group.stack)"
|
||||||
[title]="shortcuts.tip(i18n.t('connMgr.deleteStack'), 'conn-delete')"
|
[title]="shortcuts.tip(i18n.t('connMgr.deleteStack'), 'conn-delete')"
|
||||||
class="grid size-4 place-items-center text-muted-foreground hover:text-destructive"
|
class="grid size-4 place-items-center text-muted-foreground hover:text-destructive"
|
||||||
>
|
>
|
||||||
<ng-icon name="tdesignDelete" size="var(--icon-size-sm)" />
|
<ng-icon name="delete14" size="var(--icon-size-sm)" />
|
||||||
</button>
|
</button>
|
||||||
@if (group.connections.length) {
|
@if (group.connections.length) {
|
||||||
<button
|
<button
|
||||||
@@ -105,7 +105,7 @@
|
|||||||
[title]="shortcuts.tip(i18n.t('connMgr.openStack'), 'conn-connect')"
|
[title]="shortcuts.tip(i18n.t('connMgr.openStack'), 'conn-connect')"
|
||||||
class="grid size-4 place-items-center text-muted-foreground hover:text-primary"
|
class="grid size-4 place-items-center text-muted-foreground hover:text-primary"
|
||||||
>
|
>
|
||||||
<ng-icon name="tdesignArrowRight" size="var(--icon-size-sm)" />
|
<ng-icon name="arrowRight14" size="var(--icon-size-sm)" />
|
||||||
</button>
|
</button>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
@@ -123,7 +123,7 @@
|
|||||||
[class.bg-accent]="draft()?.id === connection.id"
|
[class.bg-accent]="draft()?.id === connection.id"
|
||||||
[class.opacity-50]="draggedId() === connection.id"
|
[class.opacity-50]="draggedId() === connection.id"
|
||||||
>
|
>
|
||||||
<ng-icon name="tdesignServer" size="var(--icon-size-sm)" class="shrink-0 text-muted-foreground" />
|
<ng-icon name="server14" size="var(--icon-size-sm)" class="shrink-0 text-muted-foreground" />
|
||||||
<span class="truncate">{{ connection.name }}</span>
|
<span class="truncate">{{ connection.name }}</span>
|
||||||
</button>
|
</button>
|
||||||
} @empty {
|
} @empty {
|
||||||
@@ -161,7 +161,7 @@
|
|||||||
[class.bg-accent]="draft()?.id === connection.id"
|
[class.bg-accent]="draft()?.id === connection.id"
|
||||||
[class.opacity-50]="draggedId() === connection.id"
|
[class.opacity-50]="draggedId() === connection.id"
|
||||||
>
|
>
|
||||||
<ng-icon name="tdesignServer" size="var(--icon-size-sm)" class="shrink-0 text-muted-foreground" />
|
<ng-icon name="server14" size="var(--icon-size-sm)" class="shrink-0 text-muted-foreground" />
|
||||||
<span class="truncate">{{ connection.name }}</span>
|
<span class="truncate">{{ connection.name }}</span>
|
||||||
</button>
|
</button>
|
||||||
}
|
}
|
||||||
@@ -342,7 +342,7 @@
|
|||||||
[title]="shortcuts.tip(i18n.t('common.delete'), 'conn-delete')"
|
[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"
|
class="flex items-center gap-1.5 px-2 py-1.5 text-xs text-muted-foreground hover:bg-accent hover:text-destructive"
|
||||||
>
|
>
|
||||||
<ng-icon name="tdesignDelete" size="var(--icon-size-sm)" /> {{ i18n.t('common.delete') }}
|
<ng-icon name="delete14" size="var(--icon-size-sm)" /> {{ i18n.t('common.delete') }}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
(click)="moreOpen.set(!moreOpen())"
|
(click)="moreOpen.set(!moreOpen())"
|
||||||
@@ -374,7 +374,7 @@
|
|||||||
} @else {
|
} @else {
|
||||||
<div class="grid flex-1 place-items-center">
|
<div class="grid flex-1 place-items-center">
|
||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
<ng-icon name="tdesignServer" size="var(--icon-size-xl)" class="text-muted-foreground" />
|
<ng-icon name="server24" size="var(--icon-size-xl)" class="text-muted-foreground" />
|
||||||
<p class="mt-2 text-xs text-muted-foreground">
|
<p class="mt-2 text-xs text-muted-foreground">
|
||||||
{{ i18n.t('connMgr.selectPrompt') }}
|
{{ i18n.t('connMgr.selectPrompt') }}
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ const EDGE_GAP = 6; // px on purpose: a viewport-edge margin, not a UI size
|
|||||||
<!-- the check column: a checkmark, else the item's icon -->
|
<!-- the check column: a checkmark, else the item's icon -->
|
||||||
<span class="grid w-4 shrink-0 place-items-center">
|
<span class="grid w-4 shrink-0 place-items-center">
|
||||||
@if (item.checked) {
|
@if (item.checked) {
|
||||||
<ng-icon name="tdesignCheck" size="var(--icon-size-sm)" class="text-primary" />
|
<ng-icon name="check14" size="var(--icon-size-sm)" class="text-primary" />
|
||||||
} @else if (item.icon) {
|
} @else if (item.icon) {
|
||||||
<ng-icon
|
<ng-icon
|
||||||
[name]="item.icon"
|
[name]="item.icon"
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ import { FocusService } from '@core/focus.service';
|
|||||||
</div>
|
</div>
|
||||||
<div class="mt-1.5 flex items-center gap-1.5 text-xs">
|
<div class="mt-1.5 flex items-center gap-1.5 text-xs">
|
||||||
<ng-icon
|
<ng-icon
|
||||||
[name]="isDir() ? 'tdesignFolder' : 'tdesignFile'"
|
[name]="isDir() ? 'folder14' : 'file14'"
|
||||||
size="var(--icon-size-sm)"
|
size="var(--icon-size-sm)"
|
||||||
class="shrink-0 text-muted-foreground"
|
class="shrink-0 text-muted-foreground"
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
@if (!ctx) {
|
@if (!ctx) {
|
||||||
<div class="flex flex-1 items-center justify-center">
|
<div class="flex flex-1 items-center justify-center">
|
||||||
<div class="max-w-sm border border-border bg-card p-6 text-center shadow-sm">
|
<div class="max-w-sm border border-border bg-card p-6 text-center shadow-sm">
|
||||||
<ng-icon name="tdesignCloseCircle" size="var(--icon-size-xl)" class="text-destructive" />
|
<ng-icon name="closeCircle24" size="var(--icon-size-xl)" class="text-destructive" />
|
||||||
<p class="mt-2 text-sm font-medium">{{ i18n.t('editor.noFileTitle') }}</p>
|
<p class="mt-2 text-sm font-medium">{{ i18n.t('editor.noFileTitle') }}</p>
|
||||||
<p class="mt-1 text-xs text-muted-foreground">
|
<p class="mt-1 text-xs text-muted-foreground">
|
||||||
{{ i18n.t('editor.noFileBody') }}
|
{{ i18n.t('editor.noFileBody') }}
|
||||||
@@ -20,7 +20,7 @@
|
|||||||
[class.h-9]="!isMacChrome"
|
[class.h-9]="!isMacChrome"
|
||||||
[class.pl-3]="!isMacChrome"
|
[class.pl-3]="!isMacChrome"
|
||||||
>
|
>
|
||||||
<ng-icon name="tdesignFile1" size="var(--icon-size-md)" class="shrink-0 text-muted-foreground" />
|
<ng-icon name="fileText16" size="var(--icon-size-md)" class="shrink-0 text-muted-foreground" />
|
||||||
<h1 class="min-w-0 truncate font-mono text-sm font-semibold">
|
<h1 class="min-w-0 truncate font-mono text-sm font-semibold">
|
||||||
{{ ctx.name }}{{ dirty() ? ' •' : '' }}
|
{{ ctx.name }}{{ dirty() ? ' •' : '' }}
|
||||||
</h1>
|
</h1>
|
||||||
@@ -41,13 +41,13 @@
|
|||||||
[title]="shortcuts.tip(i18n.t('editor.findHint'), 'find')"
|
[title]="shortcuts.tip(i18n.t('editor.findHint'), 'find')"
|
||||||
class="grid size-6 place-items-center text-muted-foreground electrobun-webkit-app-region-no-drag hover:bg-accent hover:text-foreground"
|
class="grid size-6 place-items-center text-muted-foreground electrobun-webkit-app-region-no-drag hover:bg-accent hover:text-foreground"
|
||||||
>
|
>
|
||||||
<ng-icon name="tdesignSearch" size="var(--icon-size-sm)" />
|
<ng-icon name="search14" size="var(--icon-size-sm)" />
|
||||||
</button>
|
</button>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
@if (findOpen()) {
|
@if (findOpen()) {
|
||||||
<div class="flex shrink-0 items-center gap-2 border-b border-border bg-toolbar px-3 py-1.5">
|
<div class="flex shrink-0 items-center gap-2 border-b border-border bg-toolbar px-3 py-1.5">
|
||||||
<ng-icon name="tdesignSearch" size="var(--icon-size-sm)" class="shrink-0 text-muted-foreground" />
|
<ng-icon name="search14" size="var(--icon-size-sm)" class="shrink-0 text-muted-foreground" />
|
||||||
<input
|
<input
|
||||||
#findBox
|
#findBox
|
||||||
[value]="findQuery()"
|
[value]="findQuery()"
|
||||||
@@ -69,14 +69,14 @@
|
|||||||
[title]="shortcuts.tipFixed(i18n.t('editor.prevMatch'), 'Shift+Enter')"
|
[title]="shortcuts.tipFixed(i18n.t('editor.prevMatch'), 'Shift+Enter')"
|
||||||
class="grid size-5 place-items-center text-muted-foreground hover:bg-accent hover:text-foreground"
|
class="grid size-5 place-items-center text-muted-foreground hover:bg-accent hover:text-foreground"
|
||||||
>
|
>
|
||||||
<ng-icon name="tdesignChevronUp" size="var(--icon-size-sm)" />
|
<ng-icon name="chevronUp14" size="var(--icon-size-sm)" />
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
(click)="findStep(1)"
|
(click)="findStep(1)"
|
||||||
[title]="shortcuts.tipFixed(i18n.t('editor.nextMatch'), 'Enter')"
|
[title]="shortcuts.tipFixed(i18n.t('editor.nextMatch'), 'Enter')"
|
||||||
class="grid size-5 place-items-center text-muted-foreground hover:bg-accent hover:text-foreground"
|
class="grid size-5 place-items-center text-muted-foreground hover:bg-accent hover:text-foreground"
|
||||||
>
|
>
|
||||||
<ng-icon name="tdesignChevronDown" size="var(--icon-size-sm)" />
|
<ng-icon name="chevronDown14" size="var(--icon-size-sm)" />
|
||||||
</button>
|
</button>
|
||||||
<div class="flex-1"></div>
|
<div class="flex-1"></div>
|
||||||
<button
|
<button
|
||||||
@@ -84,7 +84,7 @@
|
|||||||
[title]="i18n.t('editor.closeHint')"
|
[title]="i18n.t('editor.closeHint')"
|
||||||
class="grid size-5 place-items-center text-muted-foreground hover:bg-accent hover:text-foreground"
|
class="grid size-5 place-items-center text-muted-foreground hover:bg-accent hover:text-foreground"
|
||||||
>
|
>
|
||||||
<ng-icon name="tdesignClose" size="var(--icon-size-sm)" />
|
<ng-icon name="close14" size="var(--icon-size-sm)" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
@@ -93,7 +93,7 @@
|
|||||||
<div
|
<div
|
||||||
class="flex shrink-0 items-center gap-2 border-b border-border bg-destructive/10 px-3 py-1.5 text-[0.6875rem] text-destructive"
|
class="flex shrink-0 items-center gap-2 border-b border-border bg-destructive/10 px-3 py-1.5 text-[0.6875rem] text-destructive"
|
||||||
>
|
>
|
||||||
<ng-icon name="tdesignCloseCircle" size="var(--icon-size-sm)" class="shrink-0" />
|
<ng-icon name="closeCircle14" size="var(--icon-size-sm)" class="shrink-0" />
|
||||||
{{ i18n.t('editor.disconnected', { name: ctx.sessionTitle }) }}
|
{{ i18n.t('editor.disconnected', { name: ctx.sessionTitle }) }}
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
@@ -105,7 +105,7 @@
|
|||||||
} @else if (loadError(); as error) {
|
} @else if (loadError(); as error) {
|
||||||
<div class="flex flex-1 items-center justify-center">
|
<div class="flex flex-1 items-center justify-center">
|
||||||
<div class="max-w-md border border-border bg-card p-6 text-center shadow-sm">
|
<div class="max-w-md border border-border bg-card p-6 text-center shadow-sm">
|
||||||
<ng-icon name="tdesignCloseCircle" size="var(--icon-size-xl)" class="text-destructive" />
|
<ng-icon name="closeCircle24" size="var(--icon-size-xl)" class="text-destructive" />
|
||||||
<p class="mt-2 text-sm font-medium">{{ i18n.t('editor.cantOpen', { name: ctx.name }) }}</p>
|
<p class="mt-2 text-sm font-medium">{{ i18n.t('editor.cantOpen', { name: ctx.name }) }}</p>
|
||||||
<p class="mt-1 text-xs text-muted-foreground">{{ error }}</p>
|
<p class="mt-1 text-xs text-muted-foreground">{{ error }}</p>
|
||||||
<button
|
<button
|
||||||
|
|||||||
@@ -13,14 +13,14 @@
|
|||||||
<ng-icon name="tdesignLoading" class="shrink-0 animate-spin text-primary" size="var(--icon-size-sm)" />
|
<ng-icon name="tdesignLoading" class="shrink-0 animate-spin text-primary" size="var(--icon-size-sm)" />
|
||||||
} @else {
|
} @else {
|
||||||
<ng-icon
|
<ng-icon
|
||||||
[name]="isRemote() ? 'tdesignServer' : 'tdesignHardDrive'"
|
[name]="isRemote() ? 'server14' : 'hardDrive14'"
|
||||||
[class]="isRemote() ? 'shrink-0 text-primary' : 'shrink-0 text-muted-foreground'"
|
[class]="isRemote() ? 'shrink-0 text-primary' : 'shrink-0 text-muted-foreground'"
|
||||||
size="var(--icon-size-sm)"
|
size="var(--icon-size-sm)"
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
<span class="truncate">{{ isRemote() ? panel().title() : i18n.t('common.local') }}</span>
|
<span class="truncate">{{ isRemote() ? panel().title() : i18n.t('common.local') }}</span>
|
||||||
<ng-icon
|
<ng-icon
|
||||||
name="tdesignChevronDown"
|
name="chevronDown14"
|
||||||
class="shrink-0 text-muted-foreground"
|
class="shrink-0 text-muted-foreground"
|
||||||
size="var(--icon-size-sm)"
|
size="var(--icon-size-sm)"
|
||||||
/>
|
/>
|
||||||
@@ -42,7 +42,7 @@
|
|||||||
[title]="shortcuts.tip(i18n.t('panel.editFile'), 'edit')"
|
[title]="shortcuts.tip(i18n.t('panel.editFile'), 'edit')"
|
||||||
class="flex h-(--action-h) shrink-0 items-center px-1 text-muted-foreground hover:bg-accent hover:text-foreground"
|
class="flex h-(--action-h) shrink-0 items-center px-1 text-muted-foreground hover:bg-accent hover:text-foreground"
|
||||||
>
|
>
|
||||||
<ng-icon name="tdesignFile1" size="var(--icon-size-sm)" />
|
<ng-icon name="fileText14" size="var(--icon-size-sm)" />
|
||||||
<span class="ml-1 hidden text-(length:--action-font) @[30rem]:inline">{{ i18n.t('panel.edit') }}</span>
|
<span class="ml-1 hidden text-(length:--action-font) @[30rem]:inline">{{ i18n.t('panel.edit') }}</span>
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
@@ -50,7 +50,7 @@
|
|||||||
[title]="shortcuts.tip(i18n.t('panel.rename'), 'rename')"
|
[title]="shortcuts.tip(i18n.t('panel.rename'), 'rename')"
|
||||||
class="flex h-(--action-h) shrink-0 items-center px-1 text-muted-foreground hover:bg-accent hover:text-foreground"
|
class="flex h-(--action-h) shrink-0 items-center px-1 text-muted-foreground hover:bg-accent hover:text-foreground"
|
||||||
>
|
>
|
||||||
<ng-icon name="tdesignEdit" size="var(--icon-size-sm)" />
|
<ng-icon name="edit14" size="var(--icon-size-sm)" />
|
||||||
<span class="ml-1 hidden text-(length:--action-font) @[30rem]:inline">{{ i18n.t('common.rename') }}</span>
|
<span class="ml-1 hidden text-(length:--action-font) @[30rem]:inline">{{ i18n.t('common.rename') }}</span>
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
@@ -58,14 +58,14 @@
|
|||||||
[title]="shortcuts.tip(i18n.t('panel.newFolder'), 'new-folder')"
|
[title]="shortcuts.tip(i18n.t('panel.newFolder'), 'new-folder')"
|
||||||
class="grid size-(--action-h) shrink-0 place-items-center text-muted-foreground hover:bg-accent hover:text-foreground"
|
class="grid size-(--action-h) shrink-0 place-items-center text-muted-foreground hover:bg-accent hover:text-foreground"
|
||||||
>
|
>
|
||||||
<ng-icon name="tdesignFolderAdd" size="var(--icon-size-sm)" />
|
<ng-icon name="folderAdd14" size="var(--icon-size-sm)" />
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
(click)="run('new-file')"
|
(click)="run('new-file')"
|
||||||
[title]="shortcuts.tip(i18n.t('panel.newFile'), 'new-file')"
|
[title]="shortcuts.tip(i18n.t('panel.newFile'), 'new-file')"
|
||||||
class="grid size-(--action-h) shrink-0 place-items-center text-muted-foreground hover:bg-accent hover:text-foreground"
|
class="grid size-(--action-h) shrink-0 place-items-center text-muted-foreground hover:bg-accent hover:text-foreground"
|
||||||
>
|
>
|
||||||
<ng-icon name="tdesignFileAdd" size="var(--icon-size-sm)" />
|
<ng-icon name="fileAdd14" size="var(--icon-size-sm)" />
|
||||||
</button>
|
</button>
|
||||||
@if (chmodAvailable()) {
|
@if (chmodAvailable()) {
|
||||||
<button
|
<button
|
||||||
@@ -73,7 +73,7 @@
|
|||||||
[title]="shortcuts.tip(i18n.t('panel.permissions'), 'chmod')"
|
[title]="shortcuts.tip(i18n.t('panel.permissions'), 'chmod')"
|
||||||
class="grid size-(--action-h) shrink-0 place-items-center text-muted-foreground hover:bg-accent hover:text-foreground"
|
class="grid size-(--action-h) shrink-0 place-items-center text-muted-foreground hover:bg-accent hover:text-foreground"
|
||||||
>
|
>
|
||||||
<ng-icon name="tdesignLockOn" size="var(--icon-size-sm)" />
|
<ng-icon name="lockOn14" size="var(--icon-size-sm)" />
|
||||||
</button>
|
</button>
|
||||||
}
|
}
|
||||||
<button
|
<button
|
||||||
@@ -81,7 +81,7 @@
|
|||||||
[title]="shortcuts.tip(i18n.t('common.delete'), 'delete')"
|
[title]="shortcuts.tip(i18n.t('common.delete'), 'delete')"
|
||||||
class="flex h-(--action-h) shrink-0 items-center px-1 text-muted-foreground hover:bg-accent hover:text-destructive"
|
class="flex h-(--action-h) shrink-0 items-center px-1 text-muted-foreground hover:bg-accent hover:text-destructive"
|
||||||
>
|
>
|
||||||
<ng-icon name="tdesignDelete" size="var(--icon-size-sm)" />
|
<ng-icon name="delete14" size="var(--icon-size-sm)" />
|
||||||
<span class="ml-1 hidden text-(length:--action-font) @[30rem]:inline">{{ i18n.t('common.delete') }}</span>
|
<span class="ml-1 hidden text-(length:--action-font) @[30rem]:inline">{{ i18n.t('common.delete') }}</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
@@ -95,7 +95,7 @@
|
|||||||
[title]="shortcuts.tip(i18n.t('panel.parentDir'), 'parent')"
|
[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"
|
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"
|
||||||
>
|
>
|
||||||
<ng-icon name="tdesignArrowUp" size="var(--icon-size-md)" />
|
<ng-icon name="arrowUp16" size="var(--icon-size-md)" />
|
||||||
</button>
|
</button>
|
||||||
<!-- no blanket stopPropagation: the window handler lets only ⌘/Ctrl chords
|
<!-- no blanket stopPropagation: the window handler lets only ⌘/Ctrl chords
|
||||||
through from a text field, and its WebView2 reload guard must see F5 -->
|
through from a text field, and its WebView2 reload guard must see F5 -->
|
||||||
@@ -119,7 +119,7 @@
|
|||||||
@if (panel().loading()) {
|
@if (panel().loading()) {
|
||||||
<ng-icon name="tdesignLoading" size="var(--icon-size-md)" class="animate-spin" />
|
<ng-icon name="tdesignLoading" size="var(--icon-size-md)" class="animate-spin" />
|
||||||
} @else {
|
} @else {
|
||||||
<ng-icon name="tdesignRefresh" size="var(--icon-size-md)" />
|
<ng-icon name="refresh16" size="var(--icon-size-md)" />
|
||||||
}
|
}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -153,7 +153,7 @@
|
|||||||
<!-- type-to-filter bar (appears while a query is active) -->
|
<!-- type-to-filter bar (appears while a query is active) -->
|
||||||
@if (panel().filter(); as query) {
|
@if (panel().filter(); as query) {
|
||||||
<div class="flex items-center gap-1.5 border-b border-border bg-accent/50 px-2 py-1">
|
<div class="flex items-center gap-1.5 border-b border-border bg-accent/50 px-2 py-1">
|
||||||
<ng-icon name="tdesignSearch" size="var(--icon-size-sm)" class="shrink-0 text-muted-foreground" />
|
<ng-icon name="search14" size="var(--icon-size-sm)" class="shrink-0 text-muted-foreground" />
|
||||||
<span class="min-w-0 flex-1 truncate font-mono text-xs">{{ query }}</span>
|
<span class="min-w-0 flex-1 truncate font-mono text-xs">{{ query }}</span>
|
||||||
<span class="shrink-0 text-[0.6875rem] text-muted-foreground">
|
<span class="shrink-0 text-[0.6875rem] text-muted-foreground">
|
||||||
{{ i18n.t('panel.matches', { count: filterMatches() }) }}
|
{{ i18n.t('panel.matches', { count: filterMatches() }) }}
|
||||||
@@ -163,7 +163,7 @@
|
|||||||
[title]="i18n.t('panel.clearFilter')"
|
[title]="i18n.t('panel.clearFilter')"
|
||||||
class="grid size-5 shrink-0 place-items-center text-muted-foreground hover:bg-accent hover:text-foreground"
|
class="grid size-5 shrink-0 place-items-center text-muted-foreground hover:bg-accent hover:text-foreground"
|
||||||
>
|
>
|
||||||
<ng-icon name="tdesignClose" size="var(--icon-size-sm)" />
|
<ng-icon name="close14" size="var(--icon-size-sm)" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import { DialogsService } from '@core/dialogs.service';
|
|||||||
import { FileActionsService, type FileCommand, type FileTarget } from '@core/file-actions.service';
|
import { FileActionsService, type FileCommand, type FileTarget } from '@core/file-actions.service';
|
||||||
import { fileGlyph, fileIconClass, fileKindKey } from '@core/file-icons';
|
import { fileGlyph, fileIconClass, fileKindKey } from '@core/file-icons';
|
||||||
import { formatMode } from '@core/format';
|
import { formatMode } from '@core/format';
|
||||||
|
import type { Icon14, Icon16 } from '@core/icons';
|
||||||
import { remPx } from '@core/metrics';
|
import { remPx } from '@core/metrics';
|
||||||
import { PanelsService, type Side, type SortKey } from '@core/panels.service';
|
import { PanelsService, type Side, type SortKey } from '@core/panels.service';
|
||||||
import { IS_WINDOWS } from '@core/platform';
|
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. */
|
/** Input is ignored while the panel is connecting or a listing is in flight. */
|
||||||
readonly blocked = computed(() => this.panel().busy());
|
readonly blocked = computed(() => this.panel().busy());
|
||||||
/** The copy button points toward the other panel. */
|
/** The copy button points toward the other panel. */
|
||||||
readonly copyIcon = computed(() => (this.side() === 'left' ? 'tdesignArrowRight' : 'tdesignArrowLeft'));
|
readonly copyIcon = computed<Icon14>(() => (this.side() === 'left' ? 'arrowRight14' : 'arrowLeft14'));
|
||||||
|
|
||||||
readonly pathDraft = signal('');
|
readonly pathDraft = signal('');
|
||||||
/** Windows drive roots for the source picker; always empty elsewhere. */
|
/** Windows drive roots for the source picker; always empty elsewhere. */
|
||||||
@@ -192,20 +193,20 @@ export class FilePanel {
|
|||||||
{
|
{
|
||||||
label: this.i18n.t('common.local'),
|
label: this.i18n.t('common.local'),
|
||||||
action: 'local',
|
action: 'local',
|
||||||
icon: 'tdesignHardDrive',
|
icon: 'hardDrive14',
|
||||||
checked: !this.isRemote(),
|
checked: !this.isRemote(),
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
// Windows drive roots (empty list elsewhere)
|
// Windows drive roots (empty list elsewhere)
|
||||||
for (const drive of this.drives()) {
|
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();
|
const { loose, stacks } = this.connections.grouped();
|
||||||
if (loose.length || stacks.length) items.push({ type: 'separator' });
|
if (loose.length || stacks.length) items.push({ type: 'separator' });
|
||||||
const server = (connection: { id: string; name: string }): MenuItem => ({
|
const server = (connection: { id: string; name: string }): MenuItem => ({
|
||||||
label: connection.name,
|
label: connection.name,
|
||||||
action: `conn:${connection.id}`,
|
action: `conn:${connection.id}`,
|
||||||
icon: 'tdesignServer',
|
icon: 'server14',
|
||||||
});
|
});
|
||||||
items.push(...loose.map(server));
|
items.push(...loose.map(server));
|
||||||
for (const group of stacks) {
|
for (const group of stacks) {
|
||||||
@@ -218,7 +219,7 @@ export class FilePanel {
|
|||||||
items.push({
|
items.push({
|
||||||
label: host.name,
|
label: host.name,
|
||||||
action: `ssh:${host.name}`,
|
action: `ssh:${host.name}`,
|
||||||
icon: 'tdesignServer',
|
icon: 'server14',
|
||||||
disabled: !!host.unsupported,
|
disabled: !!host.unsupported,
|
||||||
tooltip: this.sshHostTitle(host),
|
tooltip: this.sshHostTitle(host),
|
||||||
});
|
});
|
||||||
@@ -267,7 +268,7 @@ export class FilePanel {
|
|||||||
return `${host.username}@${host.hostname}:${host.port}`;
|
return `${host.username}@${host.hostname}:${host.port}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
iconFor(entry: FileEntry): string {
|
iconFor(entry: FileEntry): Icon16 {
|
||||||
return fileGlyph(entry);
|
return fileGlyph(entry);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
[class.h-9]="!isMacChrome"
|
[class.h-9]="!isMacChrome"
|
||||||
[class.pl-3]="!isMacChrome"
|
[class.pl-3]="!isMacChrome"
|
||||||
>
|
>
|
||||||
<ng-icon name="tdesignSetting" size="var(--icon-size-md)" class="shrink-0 text-muted-foreground" />
|
<ng-icon name="setting16" size="var(--icon-size-md)" class="shrink-0 text-muted-foreground" />
|
||||||
<h1 class="text-sm font-semibold">{{ i18n.t('options.title') }}</h1>
|
<h1 class="text-sm font-semibold">{{ i18n.t('options.title') }}</h1>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
@@ -250,7 +250,7 @@
|
|||||||
[title]="i18n.t('options.resetDefault')"
|
[title]="i18n.t('options.resetDefault')"
|
||||||
class="grid size-5 place-items-center text-muted-foreground hover:bg-accent hover:text-foreground"
|
class="grid size-5 place-items-center text-muted-foreground hover:bg-accent hover:text-foreground"
|
||||||
>
|
>
|
||||||
<ng-icon name="tdesignClose" size="var(--icon-size-sm)" />
|
<ng-icon name="close14" size="var(--icon-size-sm)" />
|
||||||
</button>
|
</button>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
@@ -305,7 +305,7 @@
|
|||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<div class="relative min-w-0 flex-1">
|
<div class="relative min-w-0 flex-1">
|
||||||
<ng-icon
|
<ng-icon
|
||||||
name="tdesignSearch"
|
name="search14"
|
||||||
size="var(--icon-size-sm)"
|
size="var(--icon-size-sm)"
|
||||||
class="pointer-events-none absolute top-1/2 left-2 -translate-y-1/2 text-muted-foreground"
|
class="pointer-events-none absolute top-1/2 left-2 -translate-y-1/2 text-muted-foreground"
|
||||||
/>
|
/>
|
||||||
@@ -367,7 +367,7 @@
|
|||||||
[title]="i18n.t('keys.ui.remove', { key: shortcuts.format(combo) })"
|
[title]="i18n.t('keys.ui.remove', { key: shortcuts.format(combo) })"
|
||||||
class="grid size-4 place-items-center text-muted-foreground hover:text-foreground"
|
class="grid size-4 place-items-center text-muted-foreground hover:text-foreground"
|
||||||
>
|
>
|
||||||
<ng-icon name="tdesignClose" size="var(--icon-size-sm)" />
|
<ng-icon name="close14" size="var(--icon-size-sm)" />
|
||||||
</button>
|
</button>
|
||||||
</span>
|
</span>
|
||||||
}
|
}
|
||||||
@@ -387,7 +387,7 @@
|
|||||||
class="grid size-5 shrink-0 place-items-center text-muted-foreground hover:bg-accent hover:text-foreground"
|
class="grid size-5 shrink-0 place-items-center text-muted-foreground hover:bg-accent hover:text-foreground"
|
||||||
[class.text-primary]="capturing() === binding.id"
|
[class.text-primary]="capturing() === binding.id"
|
||||||
>
|
>
|
||||||
<ng-icon name="pixelAdd" size="var(--icon-size-sm)" />
|
<ng-icon name="add14" size="var(--icon-size-sm)" />
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
(click)="resetRow(binding)"
|
(click)="resetRow(binding)"
|
||||||
@@ -396,7 +396,7 @@
|
|||||||
[title]="i18n.t('keys.ui.reset')"
|
[title]="i18n.t('keys.ui.reset')"
|
||||||
class="grid size-5 shrink-0 place-items-center text-muted-foreground hover:bg-accent hover:text-foreground disabled:invisible"
|
class="grid size-5 shrink-0 place-items-center text-muted-foreground hover:bg-accent hover:text-foreground disabled:invisible"
|
||||||
>
|
>
|
||||||
<ng-icon name="tdesignRefresh" size="var(--icon-size-sm)" />
|
<ng-icon name="refresh14" size="var(--icon-size-sm)" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@if (note()?.id === binding.id) {
|
@if (note()?.id === binding.id) {
|
||||||
|
|||||||
@@ -30,12 +30,12 @@ import { TransfersService } from '@core/transfers.service';
|
|||||||
}
|
}
|
||||||
</span>
|
</span>
|
||||||
} @else if (transfers.failedJobs().length; as failed) {
|
} @else if (transfers.failedJobs().length; as failed) {
|
||||||
<ng-icon name="tdesignCloseCircle" size="var(--icon-size-sm)" class="shrink-0 text-destructive" />
|
<ng-icon name="closeCircle14" size="var(--icon-size-sm)" class="shrink-0 text-destructive" />
|
||||||
<span class="text-destructive">
|
<span class="text-destructive">
|
||||||
{{ i18n.t('transfer.failed', { count: failed }) }}
|
{{ i18n.t('transfer.failed', { count: failed }) }}
|
||||||
</span>
|
</span>
|
||||||
} @else if (transfers.hasJobs()) {
|
} @else if (transfers.hasJobs()) {
|
||||||
<ng-icon name="tdesignCheckCircle" size="var(--icon-size-sm)" class="shrink-0 text-success" />
|
<ng-icon name="checkCircle14" size="var(--icon-size-sm)" class="shrink-0 text-success" />
|
||||||
<span>{{ i18n.t('transfer.finished') }}</span>
|
<span>{{ i18n.t('transfer.finished') }}</span>
|
||||||
} @else {
|
} @else {
|
||||||
<span>{{ i18n.t('transfer.ready') }}</span>
|
<span>{{ i18n.t('transfer.ready') }}</span>
|
||||||
@@ -45,7 +45,7 @@ import { TransfersService } from '@core/transfers.service';
|
|||||||
<!-- active panel info -->
|
<!-- active panel info -->
|
||||||
<div class="flex shrink-0 items-center gap-1.5">
|
<div class="flex shrink-0 items-center gap-1.5">
|
||||||
<ng-icon
|
<ng-icon
|
||||||
[name]="activePanel().sessionId() === 'local' ? 'tdesignHardDrive' : 'tdesignServer'"
|
[name]="activePanel().sessionId() === 'local' ? 'hardDrive14' : 'server14'"
|
||||||
size="var(--icon-size-sm)"
|
size="var(--icon-size-sm)"
|
||||||
[class.text-primary]="activePanel().sessionId() !== 'local'"
|
[class.text-primary]="activePanel().sessionId() !== 'local'"
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ import { UpdaterService } from '@core/updater.service';
|
|||||||
[title]="panels.workspaceName(ws)"
|
[title]="panels.workspaceName(ws)"
|
||||||
>
|
>
|
||||||
<ng-icon
|
<ng-icon
|
||||||
[name]="panels.workspaceIsRemote(ws) ? 'tdesignServer' : 'tdesignHardDrive'"
|
[name]="panels.workspaceIsRemote(ws) ? 'server14' : 'hardDrive14'"
|
||||||
size="var(--icon-size-sm)"
|
size="var(--icon-size-sm)"
|
||||||
class="shrink-0"
|
class="shrink-0"
|
||||||
[class.text-primary]="panels.workspaceIsRemote(ws)"
|
[class.text-primary]="panels.workspaceIsRemote(ws)"
|
||||||
@@ -67,7 +67,7 @@ import { UpdaterService } from '@core/updater.service';
|
|||||||
[title]="i18n.t('tabBar.closeTab')"
|
[title]="i18n.t('tabBar.closeTab')"
|
||||||
class="mr-1 grid size-4 shrink-0 place-items-center text-muted-foreground opacity-0 group-hover:opacity-100 hover:bg-border hover:text-foreground"
|
class="mr-1 grid size-4 shrink-0 place-items-center text-muted-foreground opacity-0 group-hover:opacity-100 hover:bg-border hover:text-foreground"
|
||||||
>
|
>
|
||||||
<ng-icon name="tdesignClose" size="var(--icon-size-sm)" />
|
<ng-icon name="close14" size="var(--icon-size-sm)" />
|
||||||
</button>
|
</button>
|
||||||
} @else {
|
} @else {
|
||||||
<span class="mr-1 size-4 shrink-0"></span>
|
<span class="mr-1 size-4 shrink-0"></span>
|
||||||
@@ -83,7 +83,7 @@ import { UpdaterService } from '@core/updater.service';
|
|||||||
[title]="shortcuts.tip(i18n.t('tabBar.newTab'), 'new-tab')"
|
[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"
|
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"
|
||||||
>
|
>
|
||||||
<ng-icon name="pixelAdd" size="var(--icon-size-sm)" />
|
<ng-icon name="add14" size="var(--icon-size-sm)" />
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<div class="flex-1"></div>
|
<div class="flex-1"></div>
|
||||||
@@ -106,13 +106,13 @@ import { UpdaterService } from '@core/updater.service';
|
|||||||
<span class="relative flex items-center gap-1.5">
|
<span class="relative flex items-center gap-1.5">
|
||||||
@switch (updater.state()) {
|
@switch (updater.state()) {
|
||||||
@case ('available') {
|
@case ('available') {
|
||||||
<ng-icon name="tdesignDownload" size="var(--icon-size-sm)" />{{ i18n.t('tabBar.update') }}
|
<ng-icon name="download14" size="var(--icon-size-sm)" />{{ i18n.t('tabBar.update') }}
|
||||||
}
|
}
|
||||||
@case ('downloading') {
|
@case ('downloading') {
|
||||||
<ng-icon name="tdesignLoading" size="var(--icon-size-sm)" class="animate-spin" />{{ i18n.t('tabBar.updating') }} {{ updater.progress() }}%
|
<ng-icon name="tdesignLoading" size="var(--icon-size-sm)" class="animate-spin" />{{ i18n.t('tabBar.updating') }} {{ updater.progress() }}%
|
||||||
}
|
}
|
||||||
@case ('ready') {
|
@case ('ready') {
|
||||||
<ng-icon name="tdesignRefresh" size="var(--icon-size-sm)" />{{ i18n.t('tabBar.restartToUpdate') }}
|
<ng-icon name="refresh14" size="var(--icon-size-sm)" />{{ i18n.t('tabBar.restartToUpdate') }}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</span>
|
</span>
|
||||||
@@ -125,7 +125,7 @@ import { UpdaterService } from '@core/updater.service';
|
|||||||
[title]="shortcuts.tip(i18n.t('tabBar.manageConnections'), 'connection-manager')"
|
[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"
|
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"
|
||||||
>
|
>
|
||||||
<!-- <ng-icon name="tdesignSetting" size="var(--icon-size-sm)" /> -->
|
<!-- <ng-icon name="setting14" size="var(--icon-size-sm)" /> -->
|
||||||
{{ i18n.t('tabBar.connections') }}
|
{{ i18n.t('tabBar.connections') }}
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
@@ -135,7 +135,7 @@ import { UpdaterService } from '@core/updater.service';
|
|||||||
[title]="shortcuts.tip(i18n.t('tabBar.options'), 'open-settings')"
|
[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"
|
class="grid size-6 place-items-center text-muted-foreground electrobun-webkit-app-region-no-drag hover:bg-accent hover:text-foreground"
|
||||||
>
|
>
|
||||||
<ng-icon name="tdesignSetting" size="var(--icon-size-sm)" />
|
<ng-icon name="setting14" size="var(--icon-size-sm)" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
`,
|
`,
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { Component, inject } from '@angular/core';
|
import { Component, inject } from '@angular/core';
|
||||||
import { NgIcon } from '@ng-icons/core';
|
import { NgIcon } from '@ng-icons/core';
|
||||||
import { I18nService } from '@core/i18n.service';
|
import { I18nService } from '@core/i18n.service';
|
||||||
|
import type { Icon14 } from '@core/icons';
|
||||||
import { ToastService } from '@core/toast.service';
|
import { ToastService } from '@core/toast.service';
|
||||||
|
|
||||||
/** Toast stack overlay; shared by the main window and editor windows. */
|
/** 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')"
|
[attr.aria-label]="i18n.t('transfer.dismiss')"
|
||||||
class="shrink-0 text-muted-foreground hover:text-foreground"
|
class="shrink-0 text-muted-foreground hover:text-foreground"
|
||||||
>
|
>
|
||||||
<ng-icon name="tdesignClose" size="var(--icon-size-sm)" />
|
<ng-icon name="close14" size="var(--icon-size-sm)" />
|
||||||
</button>
|
</button>
|
||||||
<!-- auto-dismiss countdown; freezes with the timer while hovered -->
|
<!-- auto-dismiss countdown; freezes with the timer while hovered -->
|
||||||
<div
|
<div
|
||||||
@@ -45,9 +46,9 @@ export class Toasts {
|
|||||||
readonly toast = inject(ToastService);
|
readonly toast = inject(ToastService);
|
||||||
readonly i18n = inject(I18nService);
|
readonly i18n = inject(I18nService);
|
||||||
|
|
||||||
toastIcon(kind: 'error' | 'info' | 'success'): string {
|
toastIcon(kind: 'error' | 'info' | 'success'): Icon14 {
|
||||||
if (kind === 'error') return 'tdesignCloseCircle';
|
if (kind === 'error') return 'closeCircle14';
|
||||||
if (kind === 'success') return 'tdesignCheckCircle';
|
if (kind === 'success') return 'checkCircle14';
|
||||||
return 'tdesignCheck';
|
return 'check14';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
[disabled]="!transfers.finishedJobs().length"
|
[disabled]="!transfers.finishedJobs().length"
|
||||||
class="flex items-center gap-1 px-1.5 py-0.5 hover:bg-accent hover:text-foreground disabled:opacity-40 disabled:hover:bg-transparent"
|
class="flex items-center gap-1 px-1.5 py-0.5 hover:bg-accent hover:text-foreground disabled:opacity-40 disabled:hover:bg-transparent"
|
||||||
>
|
>
|
||||||
<ng-icon name="tdesignDelete" size="var(--icon-size-sm)" /> {{ i18n.t('transfer.clearFinished') }}
|
<ng-icon name="delete14" size="var(--icon-size-sm)" /> {{ i18n.t('transfer.clearFinished') }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="max-h-40 overflow-y-auto border-t border-border bg-chrome">
|
<div class="max-h-40 overflow-y-auto border-t border-border bg-chrome">
|
||||||
@@ -40,7 +40,7 @@
|
|||||||
[title]="i18n.t('transfer.cancel')"
|
[title]="i18n.t('transfer.cancel')"
|
||||||
class="shrink-0 p-1 text-muted-foreground hover:bg-accent hover:text-destructive"
|
class="shrink-0 p-1 text-muted-foreground hover:bg-accent hover:text-destructive"
|
||||||
>
|
>
|
||||||
<ng-icon name="tdesignClose" size="var(--icon-size-sm)" />
|
<ng-icon name="close14" size="var(--icon-size-sm)" />
|
||||||
</button>
|
</button>
|
||||||
} @else {
|
} @else {
|
||||||
<button
|
<button
|
||||||
@@ -48,7 +48,7 @@
|
|||||||
[title]="i18n.t('transfer.dismiss')"
|
[title]="i18n.t('transfer.dismiss')"
|
||||||
class="shrink-0 p-1 text-muted-foreground hover:bg-accent hover:text-foreground"
|
class="shrink-0 p-1 text-muted-foreground hover:bg-accent hover:text-foreground"
|
||||||
>
|
>
|
||||||
<ng-icon name="tdesignClose" size="var(--icon-size-sm)" />
|
<ng-icon name="close14" size="var(--icon-size-sm)" />
|
||||||
</button>
|
</button>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import type { TransferSnapshot } from '@shared/types';
|
|||||||
import { cycleFocus } from '@core/focus-cycle';
|
import { cycleFocus } from '@core/focus-cycle';
|
||||||
import { formatEta } from '@core/format';
|
import { formatEta } from '@core/format';
|
||||||
import { I18nService } from '@core/i18n.service';
|
import { I18nService } from '@core/i18n.service';
|
||||||
|
import type { Icon14 } from '@core/icons';
|
||||||
import { PanelsService } from '@core/panels.service';
|
import { PanelsService } from '@core/panels.service';
|
||||||
import { SettingsService } from '@core/settings.service';
|
import { SettingsService } from '@core/settings.service';
|
||||||
import { TransfersService } from '@core/transfers.service';
|
import { TransfersService } from '@core/transfers.service';
|
||||||
@@ -56,13 +57,13 @@ export class TransferBar {
|
|||||||
return parts.join(' · ');
|
return parts.join(' · ');
|
||||||
}
|
}
|
||||||
|
|
||||||
stateIcon(job: TransferSnapshot): string {
|
stateIcon(job: TransferSnapshot): Icon14 | 'tdesignLoading' {
|
||||||
switch (job.state) {
|
switch (job.state) {
|
||||||
case 'completed':
|
case 'completed':
|
||||||
return 'tdesignCheckCircle';
|
return 'checkCircle14';
|
||||||
case 'failed':
|
case 'failed':
|
||||||
case 'cancelled':
|
case 'cancelled':
|
||||||
return 'tdesignCloseCircle';
|
return 'closeCircle14';
|
||||||
default:
|
default:
|
||||||
return 'tdesignLoading';
|
return 'tdesignLoading';
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-1
@@ -204,7 +204,9 @@
|
|||||||
the text beside it, in whole pixels (small button: 12px text, 14px icon).
|
the text beside it, in whole pixels (small button: 12px text, 14px icon).
|
||||||
Every <ng-icon> takes one of these four: a one-off size like 0.9rem (14.4px)
|
Every <ng-icon> 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
|
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 {
|
:root {
|
||||||
--icon-size-sm: 0.875rem; /* 14px: beside 11-12px text, and on small icon buttons */
|
--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 */
|
--icon-size-md: 1rem; /* 16px: beside 13-14px text (file rows, window titles), path-bar buttons */
|
||||||
|
|||||||
Reference in New Issue
Block a user