From 5efce4e7cfbde41c56366fe19b7222e4b00ac149 Mon Sep 17 00:00:00 2001 From: Kang Date: Sun, 26 Jul 2026 23:21:58 +0800 Subject: [PATCH] feat(assets): vendor bundled icons from a pinned manifest Icons were previously hand-added, so their provenance and licensing lived in folklore and a mis-painted asset was invisible at runtime: GPUI's `svg()` reduces a file to an alpha mask, and `img()` keeps its pixels, but nothing complains when the two are swapped. Assets are now described by `scripts/icons.manifest` and vendored by `scripts/sync-icons.sh` from pinned upstream releases, keeping the tree reproducible and reviewable. `icons/**` stays monochrome and `color/**` full colour; `scripts/check-icon-references.sh` fails the build when a referenced asset is missing or painted through the wrong element, and runs in CI. The lookup tables move out of `formatting/` into a dedicated `features/icons` module so a ~1000-line brand table is not rebuilt inside a render closure, and saved connections gain `icon_auto_detect`, which fills in a blank icon from the detected remote system without ever overwriting a deliberate choice. --- .gitattributes | 2 + .github/workflows/architecture-boundaries.yml | 3 + AGENTS.md | 2 +- Cargo.lock | 3 + Cargo.toml | 4 + crates/nyaterm-app/Cargo.toml | 8 + .../assets/color/brand/mobaxterm.png | Bin 0 -> 1311 bytes .../assets/color/brand/windterm.png | Bin 0 -> 582 bytes .../nyaterm-app/assets/color/brand/xshell.png | Bin 0 -> 1183 bytes .../assets/color/os/alibabacloudlinux.svg | 1 + .../nyaterm-app/assets/color/os/almalinux.svg | 1 + .../assets/color/os/alpine-linux.svg | 1 + .../nyaterm-app/assets/color/os/android.svg | 1 + .../nyaterm-app/assets/color/os/anolisos.svg | 1 + crates/nyaterm-app/assets/color/os/apple.svg | 1 + .../nyaterm-app/assets/color/os/archlinux.svg | 1 + crates/nyaterm-app/assets/color/os/aws.svg | 1 + crates/nyaterm-app/assets/color/os/centos.svg | 1 + crates/nyaterm-app/assets/color/os/cmd.svg | 1 + crates/nyaterm-app/assets/color/os/debian.svg | 1 + crates/nyaterm-app/assets/color/os/deepin.svg | 1 + crates/nyaterm-app/assets/color/os/fedora.svg | 1 + crates/nyaterm-app/assets/color/os/gentoo.svg | 1 + crates/nyaterm-app/assets/color/os/git.svg | 1 + crates/nyaterm-app/assets/color/os/h3c.svg | 1 + crates/nyaterm-app/assets/color/os/huawei.svg | 1 + crates/nyaterm-app/assets/color/os/k8s.svg | 1 + .../nyaterm-app/assets/color/os/kalilinux.svg | 1 + crates/nyaterm-app/assets/color/os/kylin.svg | 1 + .../assets/color/os/linux-mint.svg | 1 + crates/nyaterm-app/assets/color/os/linux.svg | 1 + .../nyaterm-app/assets/color/os/manjaro.svg | 1 + crates/nyaterm-app/assets/color/os/nixos.svg | 1 + .../nyaterm-app/assets/color/os/openeuler.svg | 1 + .../nyaterm-app/assets/color/os/opensuse.svg | 1 + .../assets/color/os/powershell.svg | 1 + .../assets/color/os/raspberrypi.svg | 1 + .../assets/color/os/rocky-linux.svg | 1 + .../nyaterm-app/assets/color/os/tencentos.svg | 1 + crates/nyaterm-app/assets/color/os/ubuntu.svg | 1 + crates/nyaterm-app/assets/color/os/uos.svg | 53 +++ .../nyaterm-app/assets/color/os/windows.svg | 1 + crates/nyaterm-app/assets/icons/ai.svg | 2 +- .../nyaterm-app/assets/icons/ai/exec-auto.svg | 2 +- .../assets/icons/ai/exec-confirm.svg | 2 +- .../assets/icons/ai/exec-smart.svg | 2 +- .../nyaterm-app/assets/icons/ai/history.svg | 2 +- crates/nyaterm-app/assets/icons/ai/new.svg | 2 +- crates/nyaterm-app/assets/icons/ai/search.svg | 2 +- crates/nyaterm-app/assets/icons/ai/send.svg | 2 +- .../nyaterm-app/assets/icons/ai/settings.svg | 2 +- crates/nyaterm-app/assets/icons/ai/stop.svg | 2 +- .../nyaterm-app/assets/icons/arrow-down.svg | 1 + crates/nyaterm-app/assets/icons/auth.svg | 2 +- .../nyaterm-app/assets/icons/brand/apple.svg | 1 + crates/nyaterm-app/assets/icons/brand/aws.svg | 1 + .../nyaterm-app/assets/icons/brand/baidu.svg | 1 + .../assets/icons/brand/bilibili.svg | 1 + .../nyaterm-app/assets/icons/brand/bing.svg | 1 + .../nyaterm-app/assets/icons/brand/centos.svg | 1 + .../nyaterm-app/assets/icons/brand/claude.svg | 1 + crates/nyaterm-app/assets/icons/brand/css.svg | 1 + .../nyaterm-app/assets/icons/brand/debian.svg | 1 + .../nyaterm-app/assets/icons/brand/docker.svg | 1 + .../assets/icons/brand/duckduckgo.svg | 1 + .../nyaterm-app/assets/icons/brand/fedora.svg | 1 + .../nyaterm-app/assets/icons/brand/gemini.svg | 1 + .../nyaterm-app/assets/icons/brand/github.svg | 1 + .../nyaterm-app/assets/icons/brand/gitlab.svg | 1 + crates/nyaterm-app/assets/icons/brand/go.svg | 1 + .../nyaterm-app/assets/icons/brand/google.svg | 1 + .../assets/icons/brand/googlecloud.svg | 1 + .../nyaterm-app/assets/icons/brand/html5.svg | 1 + .../assets/icons/brand/javascript.svg | 1 + .../assets/icons/brand/kubernetes.svg | 1 + .../nyaterm-app/assets/icons/brand/linux.svg | 1 + .../assets/icons/brand/mongodb.svg | 1 + .../nyaterm-app/assets/icons/brand/mysql.svg | 1 + .../nyaterm-app/assets/icons/brand/nginx.svg | 1 + .../assets/icons/brand/nodedotjs.svg | 1 + .../nyaterm-app/assets/icons/brand/openai.svg | 1 + crates/nyaterm-app/assets/icons/brand/php.svg | 1 + .../assets/icons/brand/postgresql.svg | 1 + .../nyaterm-app/assets/icons/brand/python.svg | 1 + .../nyaterm-app/assets/icons/brand/redis.svg | 1 + .../nyaterm-app/assets/icons/brand/rust.svg | 1 + .../nyaterm-app/assets/icons/brand/server.svg | 1 + .../assets/icons/brand/typescript.svg | 1 + .../nyaterm-app/assets/icons/brand/ubuntu.svg | 1 + .../nyaterm-app/assets/icons/brand/yahoo.svg | 1 + .../assets/icons/brand/youtube.svg | 1 + .../nyaterm-app/assets/icons/brand/zhihu.svg | 1 + crates/nyaterm-app/assets/icons/check.svg | 2 +- .../nyaterm-app/assets/icons/chevron-down.svg | 2 +- .../nyaterm-app/assets/icons/chevron-up.svg | 2 +- crates/nyaterm-app/assets/icons/close.svg | 1 + crates/nyaterm-app/assets/icons/commands.svg | 2 +- crates/nyaterm-app/assets/icons/conn/add.svg | 2 +- .../nyaterm-app/assets/icons/conn/apple.svg | 1 - .../nyaterm-app/assets/icons/conn/connect.svg | 2 +- .../nyaterm-app/assets/icons/conn/debian.svg | 1 - .../nyaterm-app/assets/icons/conn/docker.svg | 1 - crates/nyaterm-app/assets/icons/conn/file.svg | 2 +- .../nyaterm-app/assets/icons/conn/flash.svg | 2 +- .../nyaterm-app/assets/icons/conn/folder.svg | 2 +- .../nyaterm-app/assets/icons/conn/github.svg | 1 - .../nyaterm-app/assets/icons/conn/linux.svg | 1 - crates/nyaterm-app/assets/icons/conn/more.svg | 2 +- .../nyaterm-app/assets/icons/conn/python.svg | 1 - .../nyaterm-app/assets/icons/conn/serial.svg | 2 +- .../nyaterm-app/assets/icons/conn/server.svg | 2 +- crates/nyaterm-app/assets/icons/conn/sort.svg | 2 +- .../nyaterm-app/assets/icons/conn/symlink.svg | 2 +- .../nyaterm-app/assets/icons/conn/telnet.svg | 2 +- .../assets/icons/conn/terminal.svg | 2 +- .../nyaterm-app/assets/icons/conn/ubuntu.svg | 1 - .../nyaterm-app/assets/icons/conn/windows.svg | 1 - .../nyaterm-app/assets/icons/connections.svg | 2 +- crates/nyaterm-app/assets/icons/copy.svg | 2 +- crates/nyaterm-app/assets/icons/docker.svg | 2 +- crates/nyaterm-app/assets/icons/eye.svg | 2 +- crates/nyaterm-app/assets/icons/fe/back.svg | 2 +- .../assets/icons/fe/bookmark-remove.svg | 2 +- crates/nyaterm-app/assets/icons/fe/delete.svg | 2 +- .../nyaterm-app/assets/icons/fe/download.svg | 2 +- .../nyaterm-app/assets/icons/fe/forward.svg | 2 +- .../nyaterm-app/assets/icons/fe/new-file.svg | 2 +- .../assets/icons/fe/new-folder.svg | 2 +- crates/nyaterm-app/assets/icons/fe/paste.svg | 2 +- .../nyaterm-app/assets/icons/fe/refresh.svg | 2 +- crates/nyaterm-app/assets/icons/fe/search.svg | 2 +- .../assets/icons/fe/star-outline.svg | 2 +- crates/nyaterm-app/assets/icons/fe/star.svg | 2 +- crates/nyaterm-app/assets/icons/fe/sync.svg | 2 +- crates/nyaterm-app/assets/icons/fe/up.svg | 2 +- .../assets/icons/fe/upload-folder.svg | 2 +- crates/nyaterm-app/assets/icons/fe/upload.svg | 2 +- crates/nyaterm-app/assets/icons/file/apps.svg | 1 + .../nyaterm-app/assets/icons/file/archive.svg | 1 + .../nyaterm-app/assets/icons/file/audio.svg | 1 + crates/nyaterm-app/assets/icons/file/code.svg | 1 + .../assets/icons/file/data-object.svg | 1 + .../assets/icons/file/description.svg | 1 + .../nyaterm-app/assets/icons/file/image.svg | 1 + .../nyaterm-app/assets/icons/file/movie.svg | 1 + crates/nyaterm-app/assets/icons/file/pdf.svg | 1 + .../nyaterm-app/assets/icons/file/present.svg | 1 + .../nyaterm-app/assets/icons/file/storage.svg | 1 + .../nyaterm-app/assets/icons/file/table.svg | 1 + crates/nyaterm-app/assets/icons/file/text.svg | 1 + crates/nyaterm-app/assets/icons/files.svg | 2 +- crates/nyaterm-app/assets/icons/history.svg | 2 +- crates/nyaterm-app/assets/icons/import.svg | 2 +- .../assets/icons/import/mobaxterm.svg | 27 -- .../assets/icons/import/windterm.svg | 5 - .../assets/icons/import/xshell.svg | 5 - crates/nyaterm-app/assets/icons/lock.svg | 2 +- .../nyaterm-app/assets/icons/menu/article.svg | 2 +- crates/nyaterm-app/assets/icons/menu/book.svg | 2 +- .../assets/icons/menu/broadcast.svg | 2 +- .../nyaterm-app/assets/icons/menu/export.svg | 2 +- crates/nyaterm-app/assets/icons/menu/fit.svg | 2 +- .../assets/icons/menu/horizontal.svg | 2 +- crates/nyaterm-app/assets/icons/menu/info.svg | 2 +- crates/nyaterm-app/assets/icons/menu/menu.svg | 2 +- .../nyaterm-app/assets/icons/menu/palette.svg | 2 +- .../nyaterm-app/assets/icons/menu/reset.svg | 2 +- .../nyaterm-app/assets/icons/menu/sidebar.svg | 2 +- .../nyaterm-app/assets/icons/menu/split.svg | 2 +- .../nyaterm-app/assets/icons/menu/update.svg | 2 +- .../assets/icons/menu/vertical.svg | 2 +- .../nyaterm-app/assets/icons/menu/zoom-in.svg | 2 +- .../assets/icons/menu/zoom-out.svg | 2 +- crates/nyaterm-app/assets/icons/migration.svg | 2 +- .../nyaterm-app/assets/icons/net/delete.svg | 2 +- crates/nyaterm-app/assets/icons/net/edit.svg | 2 +- crates/nyaterm-app/assets/icons/net/move.svg | 2 +- crates/nyaterm-app/assets/icons/network.svg | 2 +- crates/nyaterm-app/assets/icons/pin.svg | 2 +- crates/nyaterm-app/assets/icons/processes.svg | 2 +- crates/nyaterm-app/assets/icons/quote.svg | 2 +- crates/nyaterm-app/assets/icons/record.svg | 2 +- crates/nyaterm-app/assets/icons/resources.svg | 2 +- crates/nyaterm-app/assets/icons/send.svg | 2 +- .../assets/icons/session/disconnect.svg | 2 +- .../nyaterm-app/assets/icons/session/more.svg | 2 +- .../assets/icons/session/reconnect.svg | 2 +- .../assets/icons/session/record.svg | 2 +- .../assets/icons/session/rename.svg | 2 +- .../nyaterm-app/assets/icons/session/save.svg | 2 +- .../nyaterm-app/assets/icons/session/stop.svg | 2 +- crates/nyaterm-app/assets/icons/sessions.svg | 2 +- crates/nyaterm-app/assets/icons/settings.svg | 2 +- crates/nyaterm-app/assets/icons/sync.svg | 2 +- .../assets/icons/transfer/clear-all.svg | 2 +- .../assets/icons/transfer/pause.svg | 2 +- .../assets/icons/transfer/play.svg | 2 +- .../assets/icons/transfer/playlist-remove.svg | 2 +- .../assets/icons/transfer/stop.svg | 2 +- .../nyaterm-app/assets/icons/translation.svg | 2 +- .../nyaterm-app/assets/icons/view-compact.svg | 2 +- crates/nyaterm-app/assets/icons/view-grid.svg | 2 +- crates/nyaterm-app/assets/icons/view-list.svg | 2 +- crates/nyaterm-app/assets/icons/warning.svg | 1 + .../nyaterm-app/assets/icons/window/close.svg | 2 +- .../assets/icons/window/maximize.svg | 2 +- .../assets/icons/window/minimize.svg | 2 +- .../assets/icons/window/restore.svg | 2 +- crates/nyaterm-app/assets/icons/workspace.svg | 1 + crates/nyaterm-app/build.rs | 7 + crates/nyaterm-app/src/assets.rs | 376 +++++----------- crates/nyaterm-app/src/lib.rs | 7 + crates/nyaterm-app/src/main.rs | 3 +- crates/nyaterm-core/src/cloud_sync.rs | 1 + crates/nyaterm-core/src/models.rs | 63 +++ crates/nyaterm-core/src/session_import.rs | 4 + crates/nyaterm-core/src/storage.rs | 10 + .../src/features/commands/mod.rs | 2 +- .../commands/quick_command_runtime/mod.rs | 33 -- .../connection_runtime/auto_icon.rs | 73 +++ .../connections/connection_runtime/editor.rs | 13 + .../connections/connection_runtime/helpers.rs | 10 + .../connections/connection_runtime/mod.rs | 1 + .../features/connections/connections/mod.rs | 8 +- .../connections/state/editor_logic.rs | 17 + .../features/formatting/connection_icons.rs | 193 -------- .../src/features/formatting/mod.rs | 3 - .../src/features/icons/aliases.rs | 117 +++++ .../src/features/icons/connection.rs | 418 ++++++++++++++++++ .../src/features/icons/file_kind.rs | 188 ++++++++ .../nyaterm-desktop/src/features/icons/mod.rs | 164 +++++++ .../src/features/icons/quick.rs | 106 +++++ .../src/features/icons/remote_system.rs | 198 +++++++++ .../src/features/icons/search.rs | 86 ++++ .../inspector/ai_widgets/transcript.rs | 8 +- .../src/features/inspector/commands.rs | 11 +- .../layout/workspace/surface/menus.rs | 1 + crates/nyaterm-desktop/src/features/mod.rs | 10 +- .../nyaterm-desktop/src/features/pages/mod.rs | 2 +- .../src/features/pages/settings/ai/section.rs | 8 +- .../src/features/pages/settings/mod.rs | 2 +- .../pages/settings/terminal/helpers.rs | 57 --- .../src/features/pages/transfers/entry_row.rs | 4 +- .../panels/connection_import_overlay.rs | 15 +- .../src/features/panels/helpers.rs | 64 +-- .../panels/temporary_ssh_link_overlay.rs | 11 +- .../features/remote/remote_runtime/stats.rs | 3 + .../features/shell/activity_bar_runtime.rs | 14 +- .../src/features/shell/tab_mouse.rs | 6 +- .../terminal_context_menu_runtime/helpers.rs | 45 +- .../terminal_context_menu_runtime/menu.rs | 8 +- .../src/features/view_widgets/icons.rs | 146 +++--- .../src/features/view_widgets/mod.rs | 2 +- .../nyaterm-desktop/src/models/navigation.rs | 48 +- .../src/models/session_event_bridge.rs | 18 +- crates/nyaterm-ui/src/lib.rs | 4 +- docs/architecture/gpui-migration-status.md | 20 + docs/third-party-icons.md | 58 +++ scripts/icons.manifest | 245 ++++++++++ scripts/sync-icons.sh | 209 +++++++++ 260 files changed, 2597 insertions(+), 932 deletions(-) create mode 100644 .gitattributes create mode 100644 crates/nyaterm-app/assets/color/brand/mobaxterm.png create mode 100644 crates/nyaterm-app/assets/color/brand/windterm.png create mode 100644 crates/nyaterm-app/assets/color/brand/xshell.png create mode 100644 crates/nyaterm-app/assets/color/os/alibabacloudlinux.svg create mode 100644 crates/nyaterm-app/assets/color/os/almalinux.svg create mode 100644 crates/nyaterm-app/assets/color/os/alpine-linux.svg create mode 100644 crates/nyaterm-app/assets/color/os/android.svg create mode 100644 crates/nyaterm-app/assets/color/os/anolisos.svg create mode 100644 crates/nyaterm-app/assets/color/os/apple.svg create mode 100644 crates/nyaterm-app/assets/color/os/archlinux.svg create mode 100644 crates/nyaterm-app/assets/color/os/aws.svg create mode 100644 crates/nyaterm-app/assets/color/os/centos.svg create mode 100644 crates/nyaterm-app/assets/color/os/cmd.svg create mode 100644 crates/nyaterm-app/assets/color/os/debian.svg create mode 100644 crates/nyaterm-app/assets/color/os/deepin.svg create mode 100644 crates/nyaterm-app/assets/color/os/fedora.svg create mode 100644 crates/nyaterm-app/assets/color/os/gentoo.svg create mode 100644 crates/nyaterm-app/assets/color/os/git.svg create mode 100644 crates/nyaterm-app/assets/color/os/h3c.svg create mode 100644 crates/nyaterm-app/assets/color/os/huawei.svg create mode 100644 crates/nyaterm-app/assets/color/os/k8s.svg create mode 100644 crates/nyaterm-app/assets/color/os/kalilinux.svg create mode 100644 crates/nyaterm-app/assets/color/os/kylin.svg create mode 100644 crates/nyaterm-app/assets/color/os/linux-mint.svg create mode 100644 crates/nyaterm-app/assets/color/os/linux.svg create mode 100644 crates/nyaterm-app/assets/color/os/manjaro.svg create mode 100644 crates/nyaterm-app/assets/color/os/nixos.svg create mode 100644 crates/nyaterm-app/assets/color/os/openeuler.svg create mode 100644 crates/nyaterm-app/assets/color/os/opensuse.svg create mode 100644 crates/nyaterm-app/assets/color/os/powershell.svg create mode 100644 crates/nyaterm-app/assets/color/os/raspberrypi.svg create mode 100644 crates/nyaterm-app/assets/color/os/rocky-linux.svg create mode 100644 crates/nyaterm-app/assets/color/os/tencentos.svg create mode 100644 crates/nyaterm-app/assets/color/os/ubuntu.svg create mode 100644 crates/nyaterm-app/assets/color/os/uos.svg create mode 100644 crates/nyaterm-app/assets/color/os/windows.svg create mode 100644 crates/nyaterm-app/assets/icons/arrow-down.svg create mode 100644 crates/nyaterm-app/assets/icons/brand/apple.svg create mode 100644 crates/nyaterm-app/assets/icons/brand/aws.svg create mode 100644 crates/nyaterm-app/assets/icons/brand/baidu.svg create mode 100644 crates/nyaterm-app/assets/icons/brand/bilibili.svg create mode 100644 crates/nyaterm-app/assets/icons/brand/bing.svg create mode 100644 crates/nyaterm-app/assets/icons/brand/centos.svg create mode 100644 crates/nyaterm-app/assets/icons/brand/claude.svg create mode 100644 crates/nyaterm-app/assets/icons/brand/css.svg create mode 100644 crates/nyaterm-app/assets/icons/brand/debian.svg create mode 100644 crates/nyaterm-app/assets/icons/brand/docker.svg create mode 100644 crates/nyaterm-app/assets/icons/brand/duckduckgo.svg create mode 100644 crates/nyaterm-app/assets/icons/brand/fedora.svg create mode 100644 crates/nyaterm-app/assets/icons/brand/gemini.svg create mode 100644 crates/nyaterm-app/assets/icons/brand/github.svg create mode 100644 crates/nyaterm-app/assets/icons/brand/gitlab.svg create mode 100644 crates/nyaterm-app/assets/icons/brand/go.svg create mode 100644 crates/nyaterm-app/assets/icons/brand/google.svg create mode 100644 crates/nyaterm-app/assets/icons/brand/googlecloud.svg create mode 100644 crates/nyaterm-app/assets/icons/brand/html5.svg create mode 100644 crates/nyaterm-app/assets/icons/brand/javascript.svg create mode 100644 crates/nyaterm-app/assets/icons/brand/kubernetes.svg create mode 100644 crates/nyaterm-app/assets/icons/brand/linux.svg create mode 100644 crates/nyaterm-app/assets/icons/brand/mongodb.svg create mode 100644 crates/nyaterm-app/assets/icons/brand/mysql.svg create mode 100644 crates/nyaterm-app/assets/icons/brand/nginx.svg create mode 100644 crates/nyaterm-app/assets/icons/brand/nodedotjs.svg create mode 100644 crates/nyaterm-app/assets/icons/brand/openai.svg create mode 100644 crates/nyaterm-app/assets/icons/brand/php.svg create mode 100644 crates/nyaterm-app/assets/icons/brand/postgresql.svg create mode 100644 crates/nyaterm-app/assets/icons/brand/python.svg create mode 100644 crates/nyaterm-app/assets/icons/brand/redis.svg create mode 100644 crates/nyaterm-app/assets/icons/brand/rust.svg create mode 100644 crates/nyaterm-app/assets/icons/brand/server.svg create mode 100644 crates/nyaterm-app/assets/icons/brand/typescript.svg create mode 100644 crates/nyaterm-app/assets/icons/brand/ubuntu.svg create mode 100644 crates/nyaterm-app/assets/icons/brand/yahoo.svg create mode 100644 crates/nyaterm-app/assets/icons/brand/youtube.svg create mode 100644 crates/nyaterm-app/assets/icons/brand/zhihu.svg create mode 100644 crates/nyaterm-app/assets/icons/close.svg delete mode 100644 crates/nyaterm-app/assets/icons/conn/apple.svg delete mode 100644 crates/nyaterm-app/assets/icons/conn/debian.svg delete mode 100644 crates/nyaterm-app/assets/icons/conn/docker.svg delete mode 100644 crates/nyaterm-app/assets/icons/conn/github.svg delete mode 100644 crates/nyaterm-app/assets/icons/conn/linux.svg delete mode 100644 crates/nyaterm-app/assets/icons/conn/python.svg delete mode 100644 crates/nyaterm-app/assets/icons/conn/ubuntu.svg delete mode 100644 crates/nyaterm-app/assets/icons/conn/windows.svg create mode 100644 crates/nyaterm-app/assets/icons/file/apps.svg create mode 100644 crates/nyaterm-app/assets/icons/file/archive.svg create mode 100644 crates/nyaterm-app/assets/icons/file/audio.svg create mode 100644 crates/nyaterm-app/assets/icons/file/code.svg create mode 100644 crates/nyaterm-app/assets/icons/file/data-object.svg create mode 100644 crates/nyaterm-app/assets/icons/file/description.svg create mode 100644 crates/nyaterm-app/assets/icons/file/image.svg create mode 100644 crates/nyaterm-app/assets/icons/file/movie.svg create mode 100644 crates/nyaterm-app/assets/icons/file/pdf.svg create mode 100644 crates/nyaterm-app/assets/icons/file/present.svg create mode 100644 crates/nyaterm-app/assets/icons/file/storage.svg create mode 100644 crates/nyaterm-app/assets/icons/file/table.svg create mode 100644 crates/nyaterm-app/assets/icons/file/text.svg delete mode 100644 crates/nyaterm-app/assets/icons/import/mobaxterm.svg delete mode 100644 crates/nyaterm-app/assets/icons/import/windterm.svg delete mode 100644 crates/nyaterm-app/assets/icons/import/xshell.svg create mode 100644 crates/nyaterm-app/assets/icons/warning.svg create mode 100644 crates/nyaterm-app/assets/icons/workspace.svg create mode 100644 crates/nyaterm-app/build.rs create mode 100644 crates/nyaterm-app/src/lib.rs create mode 100644 crates/nyaterm-desktop/src/features/connections/connection_runtime/auto_icon.rs delete mode 100644 crates/nyaterm-desktop/src/features/formatting/connection_icons.rs create mode 100644 crates/nyaterm-desktop/src/features/icons/aliases.rs create mode 100644 crates/nyaterm-desktop/src/features/icons/connection.rs create mode 100644 crates/nyaterm-desktop/src/features/icons/file_kind.rs create mode 100644 crates/nyaterm-desktop/src/features/icons/mod.rs create mode 100644 crates/nyaterm-desktop/src/features/icons/quick.rs create mode 100644 crates/nyaterm-desktop/src/features/icons/remote_system.rs create mode 100644 crates/nyaterm-desktop/src/features/icons/search.rs create mode 100644 docs/third-party-icons.md create mode 100644 scripts/icons.manifest create mode 100644 scripts/sync-icons.sh diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..f3fdae91 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +scripts/icons.manifest -text +scripts/*.sh text eol=lf diff --git a/.github/workflows/architecture-boundaries.yml b/.github/workflows/architecture-boundaries.yml index ae36b7e4..a3093fc6 100644 --- a/.github/workflows/architecture-boundaries.yml +++ b/.github/workflows/architecture-boundaries.yml @@ -15,3 +15,6 @@ jobs: run: sudo apt-get update && sudo apt-get install -y ripgrep - name: Check architecture boundaries run: bash scripts/check-architecture-boundaries.sh + # A missing or mis-painted icon is silent at runtime, so it has to be caught here. + - name: Check icon references + run: bash scripts/check-icon-references.sh diff --git a/AGENTS.md b/AGENTS.md index 51b26975..cb211528 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -20,7 +20,7 @@ This is a Rust 2024 Cargo workspace. * `crates/nyaterm-store`: transitional persistence façade. The implementation currently remains in `nyaterm-core`; do not expand the re-export-only boundary without either moving the implementation or updating consumers to use this crate. * `crates/nyaterm-legacy`: migration and legacy-source compatibility only. Do not add new product features here. * `crates/nyaterm-otp`: bundled OTP implementation used for HOTP/TOTP compatibility. -* `crates/nyaterm-app/assets`: application SVG assets. +* `crates/nyaterm-app/assets`: bundled icon assets. `icons/**` is monochrome and painted with `svg()`; `color/**` is full color and painted with `img()`. The two are not interchangeable — see `docs/third-party-icons.md`. Assets are vendored by `scripts/sync-icons.sh` from `scripts/icons.manifest` and committed, so add or change icons through the manifest rather than by hand. * `vendor/`: modified or pinned third-party dependencies such as `russh`, `russh-sftp`, and `zmodem2`. ## Architectural Rules diff --git a/Cargo.lock b/Cargo.lock index 70aa8517..450440b2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4212,9 +4212,11 @@ dependencies = [ "gpui", "nyaterm-core", "nyaterm-desktop", + "rust-embed", "tracing", "tracing-appender", "tracing-subscriber", + "usvg", ] [[package]] @@ -4299,6 +4301,7 @@ dependencies = [ "alacritty_terminal", "encoding_rs", "flate2", + "memchr", "unicode-width", ] diff --git a/Cargo.toml b/Cargo.toml index c2a57879..8a9a78fd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -55,6 +55,7 @@ redb = "4.1.0" regex = "1.11.1" russh = { path = "vendor/russh/russh", features = ["des", "dsa"] } russh-sftp = { path = "vendor/russh-sftp" } +rust-embed = { version = "8.11", features = ["include-exclude", "debug-embed"] } serialport = { version = "4", default-features = false } serde = { version = "1", features = ["derive"] } serde_json = { version = "1", features = ["raw_value"] } @@ -68,6 +69,9 @@ tokio-socks = "0.5.2" tracing = "0.1" tracing-appender = "0.2.4" tracing-subscriber = { version = "0.3", features = ["env-filter", "json"] } +# Test-only. Feature set must mirror gpui's own usvg dependency (default-features +# = false) so asset tests cannot pass under capabilities the app does not build. +usvg = { version = "0.45", default-features = false } uuid = { version = "1", features = ["v4", "serde"] } vte = "0.14" zmodem2 = { path = "vendor/zmodem2" } diff --git a/crates/nyaterm-app/Cargo.toml b/crates/nyaterm-app/Cargo.toml index c36d4c04..c800e17e 100644 --- a/crates/nyaterm-app/Cargo.toml +++ b/crates/nyaterm-app/Cargo.toml @@ -11,10 +11,18 @@ anyhow.workspace = true gpui.workspace = true nyaterm-core.workspace = true nyaterm-desktop.workspace = true +rust-embed.workspace = true tracing.workspace = true tracing-appender.workspace = true tracing-subscriber.workspace = true +[dev-dependencies] +usvg.workspace = true + +[lib] +name = "nyaterm_app" +path = "src/lib.rs" + [[bin]] name = "nyaterm" path = "src/main.rs" diff --git a/crates/nyaterm-app/assets/color/brand/mobaxterm.png b/crates/nyaterm-app/assets/color/brand/mobaxterm.png new file mode 100644 index 0000000000000000000000000000000000000000..6fa3021f9ed272d648b80bc10cd631a0434b25ba GIT binary patch literal 1311 zcmZ{keN@tC6vr=;U>M7jS;@+*O)Fc57>K3@1uCIL_y&R#6jF3bDR$JO4XLQioocn_ z(v;MhJ1RLUb}~wvFJX9cD{3>7UeJH;L+8!9wlHO`F~bg~gh|Oszem&Q4D(X!7zJVzDlYbZX;NveElWFj{OanS88`iNT;8d!Z0+^acjtFI z-SRcd)C9sHKrfwsT_A25_BBjnD>`z{O?eDFD)^c}=w82mG<~b_iKo$l{_ZzYv7es_ zz(^+F7bbO$&DD*&=XKx;`@pPL52Df5#6(wm`adOkXD61G5BTit!woR+HqHxqW_&q)?Jzp< z!N8x-?^*5Q#clT*+hDatkf;WWP`03r6oJi}SKys8TDN zj)rQE9bB!S?QdxMOmD2cxNn7@qOA0t-EMbCf?m$`nB&H~vMk!)i=ff@M=mFkg6>cQ z7ksgFVS}p0KV;QBk8cxs^c(VLzP>p)(plQ8k?;#*XX#sC_q`^Zx=K%f)gKVe<)&%I z4;CI)N|^zCJ&uv|hjwLa2fD3Q0h`GbGJ5d%ZHm-=a>+@tPx@HYrHTrtcX=RZyf)IE zb;TmT*|Ix%sAdE^>+H5+)YhUS&DvNOpr5asOtEeH3I$!?Z(97 zw$!4+5G}f(UT!S+FWpsntId7yB4TCd{??k3jD|gra#zVQ?X;Mk4RuEK!`uKFW-*Of z+;GB?i%-DR(b&T5^EyAYpH?9@(U4!Fr({y%L_PFGb`zjv@|9AFQWlX_C_@5}h$PAy zVklxs#IOh=F@hAj8c`zAk>GRWe?Y!MD&JQ6ap0g=#~K7!`QHSkJYQC%l;m&!ICIy% Q`W3YNksCioPB58wm(Dn3E?W&%?10T!FtB;!T9%XV8g ziP*}Dzng(%K7O1Ghkv}|UjI0WF=0R^P?R7hhhcaqs!D7eMB%M_-ix9hW6X@eq=1qb zlM5*?lyhn;0_zBUKRmstvNAs%S}&4{c4v{p)=M$u{DT(CT|VIF%;ptG~n z(Q&M`QkpO(t)wOZ+7)7VWpaaV_fLV7e*c6K`BK3p7fr$Mdp(8qN4*{81g=dWNHQd6drA=bIVzMY?lCjQy<;qF$Bc$4gmltW@tVx>Ejft&o?!GyRiB-3CTn)D>3Lxs4;uV z1kxUhF|0xyDde+`;G5&#OQevmI;=En1eKKQe}q%07%v{0034- z&yV;;VOa`c5|WAfE;)S~_pXt#|KRm?UNxH<)V35|L{SU?z*Jy!5@{)P-aX?`bTm_e z&B9_OxHErw~y?qqIsg zTs5C;K-x&u9gAx*#l)j@e#~An!O=SI^7!N0^X?hPu>Wu2V>Y`6AQCwM086KFik&?I z1FIW-2N6&T2>@WkFAA-5D=|xj)2DGmXRWSKb@@N*7t1z!$}GK#tZ#LrkEL}SkJ9<* zTG5cOOon~pfmvv>u+7%pO20AZb>3QtZ7Vfl<-2S7ug(#_DAb(FssR94j#|qa0D#u{ z*oflc$I`cYl|}OT^ipH%+)7KlQLA9sCkkK8;ANqPOBux1~A*|p;9O7zlEY^wJF-U{M^!3TM2oqfBYKHt=M(`s%%3;}>hpZBjm z=^I#k(vPJeo)n*Xkd{Kl49(jz4Yn)!Z#1d{x;*} \ No newline at end of file diff --git a/crates/nyaterm-app/assets/color/os/almalinux.svg b/crates/nyaterm-app/assets/color/os/almalinux.svg new file mode 100644 index 00000000..12a9bbe3 --- /dev/null +++ b/crates/nyaterm-app/assets/color/os/almalinux.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/crates/nyaterm-app/assets/color/os/alpine-linux.svg b/crates/nyaterm-app/assets/color/os/alpine-linux.svg new file mode 100644 index 00000000..e5411687 --- /dev/null +++ b/crates/nyaterm-app/assets/color/os/alpine-linux.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/crates/nyaterm-app/assets/color/os/android.svg b/crates/nyaterm-app/assets/color/os/android.svg new file mode 100644 index 00000000..f04349ec --- /dev/null +++ b/crates/nyaterm-app/assets/color/os/android.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/crates/nyaterm-app/assets/color/os/anolisos.svg b/crates/nyaterm-app/assets/color/os/anolisos.svg new file mode 100644 index 00000000..70efddc8 --- /dev/null +++ b/crates/nyaterm-app/assets/color/os/anolisos.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/crates/nyaterm-app/assets/color/os/apple.svg b/crates/nyaterm-app/assets/color/os/apple.svg new file mode 100644 index 00000000..942531cf --- /dev/null +++ b/crates/nyaterm-app/assets/color/os/apple.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/crates/nyaterm-app/assets/color/os/archlinux.svg b/crates/nyaterm-app/assets/color/os/archlinux.svg new file mode 100644 index 00000000..cd0c1527 --- /dev/null +++ b/crates/nyaterm-app/assets/color/os/archlinux.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/crates/nyaterm-app/assets/color/os/aws.svg b/crates/nyaterm-app/assets/color/os/aws.svg new file mode 100644 index 00000000..ca0b8e39 --- /dev/null +++ b/crates/nyaterm-app/assets/color/os/aws.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/crates/nyaterm-app/assets/color/os/centos.svg b/crates/nyaterm-app/assets/color/os/centos.svg new file mode 100644 index 00000000..d60ad8ac --- /dev/null +++ b/crates/nyaterm-app/assets/color/os/centos.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/crates/nyaterm-app/assets/color/os/cmd.svg b/crates/nyaterm-app/assets/color/os/cmd.svg new file mode 100644 index 00000000..3bf17788 --- /dev/null +++ b/crates/nyaterm-app/assets/color/os/cmd.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/crates/nyaterm-app/assets/color/os/debian.svg b/crates/nyaterm-app/assets/color/os/debian.svg new file mode 100644 index 00000000..edf539ef --- /dev/null +++ b/crates/nyaterm-app/assets/color/os/debian.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/crates/nyaterm-app/assets/color/os/deepin.svg b/crates/nyaterm-app/assets/color/os/deepin.svg new file mode 100644 index 00000000..a76c9f87 --- /dev/null +++ b/crates/nyaterm-app/assets/color/os/deepin.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/crates/nyaterm-app/assets/color/os/fedora.svg b/crates/nyaterm-app/assets/color/os/fedora.svg new file mode 100644 index 00000000..19b74712 --- /dev/null +++ b/crates/nyaterm-app/assets/color/os/fedora.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/crates/nyaterm-app/assets/color/os/gentoo.svg b/crates/nyaterm-app/assets/color/os/gentoo.svg new file mode 100644 index 00000000..08ea42af --- /dev/null +++ b/crates/nyaterm-app/assets/color/os/gentoo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/crates/nyaterm-app/assets/color/os/git.svg b/crates/nyaterm-app/assets/color/os/git.svg new file mode 100644 index 00000000..a2665c53 --- /dev/null +++ b/crates/nyaterm-app/assets/color/os/git.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/crates/nyaterm-app/assets/color/os/h3c.svg b/crates/nyaterm-app/assets/color/os/h3c.svg new file mode 100644 index 00000000..f5396963 --- /dev/null +++ b/crates/nyaterm-app/assets/color/os/h3c.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/crates/nyaterm-app/assets/color/os/huawei.svg b/crates/nyaterm-app/assets/color/os/huawei.svg new file mode 100644 index 00000000..4f4a1a9c --- /dev/null +++ b/crates/nyaterm-app/assets/color/os/huawei.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/crates/nyaterm-app/assets/color/os/k8s.svg b/crates/nyaterm-app/assets/color/os/k8s.svg new file mode 100644 index 00000000..f34657db --- /dev/null +++ b/crates/nyaterm-app/assets/color/os/k8s.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/crates/nyaterm-app/assets/color/os/kalilinux.svg b/crates/nyaterm-app/assets/color/os/kalilinux.svg new file mode 100644 index 00000000..779bba09 --- /dev/null +++ b/crates/nyaterm-app/assets/color/os/kalilinux.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/crates/nyaterm-app/assets/color/os/kylin.svg b/crates/nyaterm-app/assets/color/os/kylin.svg new file mode 100644 index 00000000..0f8239e2 --- /dev/null +++ b/crates/nyaterm-app/assets/color/os/kylin.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/crates/nyaterm-app/assets/color/os/linux-mint.svg b/crates/nyaterm-app/assets/color/os/linux-mint.svg new file mode 100644 index 00000000..1ec1f517 --- /dev/null +++ b/crates/nyaterm-app/assets/color/os/linux-mint.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/crates/nyaterm-app/assets/color/os/linux.svg b/crates/nyaterm-app/assets/color/os/linux.svg new file mode 100644 index 00000000..3c600b80 --- /dev/null +++ b/crates/nyaterm-app/assets/color/os/linux.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/crates/nyaterm-app/assets/color/os/manjaro.svg b/crates/nyaterm-app/assets/color/os/manjaro.svg new file mode 100644 index 00000000..98118a68 --- /dev/null +++ b/crates/nyaterm-app/assets/color/os/manjaro.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/crates/nyaterm-app/assets/color/os/nixos.svg b/crates/nyaterm-app/assets/color/os/nixos.svg new file mode 100644 index 00000000..a5abcdbc --- /dev/null +++ b/crates/nyaterm-app/assets/color/os/nixos.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/crates/nyaterm-app/assets/color/os/openeuler.svg b/crates/nyaterm-app/assets/color/os/openeuler.svg new file mode 100644 index 00000000..faefef8f --- /dev/null +++ b/crates/nyaterm-app/assets/color/os/openeuler.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/crates/nyaterm-app/assets/color/os/opensuse.svg b/crates/nyaterm-app/assets/color/os/opensuse.svg new file mode 100644 index 00000000..9e510aeb --- /dev/null +++ b/crates/nyaterm-app/assets/color/os/opensuse.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/crates/nyaterm-app/assets/color/os/powershell.svg b/crates/nyaterm-app/assets/color/os/powershell.svg new file mode 100644 index 00000000..c960d455 --- /dev/null +++ b/crates/nyaterm-app/assets/color/os/powershell.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/crates/nyaterm-app/assets/color/os/raspberrypi.svg b/crates/nyaterm-app/assets/color/os/raspberrypi.svg new file mode 100644 index 00000000..4990ba94 --- /dev/null +++ b/crates/nyaterm-app/assets/color/os/raspberrypi.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/crates/nyaterm-app/assets/color/os/rocky-linux.svg b/crates/nyaterm-app/assets/color/os/rocky-linux.svg new file mode 100644 index 00000000..b8569dfb --- /dev/null +++ b/crates/nyaterm-app/assets/color/os/rocky-linux.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/crates/nyaterm-app/assets/color/os/tencentos.svg b/crates/nyaterm-app/assets/color/os/tencentos.svg new file mode 100644 index 00000000..3677bedb --- /dev/null +++ b/crates/nyaterm-app/assets/color/os/tencentos.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/crates/nyaterm-app/assets/color/os/ubuntu.svg b/crates/nyaterm-app/assets/color/os/ubuntu.svg new file mode 100644 index 00000000..4875ee3f --- /dev/null +++ b/crates/nyaterm-app/assets/color/os/ubuntu.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/crates/nyaterm-app/assets/color/os/uos.svg b/crates/nyaterm-app/assets/color/os/uos.svg new file mode 100644 index 00000000..b1fdafeb --- /dev/null +++ b/crates/nyaterm-app/assets/color/os/uos.svg @@ -0,0 +1,53 @@ + + UOS + \ No newline at end of file diff --git a/crates/nyaterm-app/assets/color/os/windows.svg b/crates/nyaterm-app/assets/color/os/windows.svg new file mode 100644 index 00000000..9199d0a0 --- /dev/null +++ b/crates/nyaterm-app/assets/color/os/windows.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/crates/nyaterm-app/assets/icons/ai.svg b/crates/nyaterm-app/assets/icons/ai.svg index 7df01597..b7d5cd1a 100644 --- a/crates/nyaterm-app/assets/icons/ai.svg +++ b/crates/nyaterm-app/assets/icons/ai.svg @@ -1 +1 @@ - + \ No newline at end of file diff --git a/crates/nyaterm-app/assets/icons/ai/exec-auto.svg b/crates/nyaterm-app/assets/icons/ai/exec-auto.svg index 423f03f2..05fbc02c 100644 --- a/crates/nyaterm-app/assets/icons/ai/exec-auto.svg +++ b/crates/nyaterm-app/assets/icons/ai/exec-auto.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/crates/nyaterm-app/assets/icons/ai/exec-confirm.svg b/crates/nyaterm-app/assets/icons/ai/exec-confirm.svg index 215fe171..8a87c75c 100644 --- a/crates/nyaterm-app/assets/icons/ai/exec-confirm.svg +++ b/crates/nyaterm-app/assets/icons/ai/exec-confirm.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/crates/nyaterm-app/assets/icons/ai/exec-smart.svg b/crates/nyaterm-app/assets/icons/ai/exec-smart.svg index f7bcb4ec..f8510015 100644 --- a/crates/nyaterm-app/assets/icons/ai/exec-smart.svg +++ b/crates/nyaterm-app/assets/icons/ai/exec-smart.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/crates/nyaterm-app/assets/icons/ai/history.svg b/crates/nyaterm-app/assets/icons/ai/history.svg index 31af58cb..f8f018c8 100644 --- a/crates/nyaterm-app/assets/icons/ai/history.svg +++ b/crates/nyaterm-app/assets/icons/ai/history.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/crates/nyaterm-app/assets/icons/ai/new.svg b/crates/nyaterm-app/assets/icons/ai/new.svg index 09320265..58f73e96 100644 --- a/crates/nyaterm-app/assets/icons/ai/new.svg +++ b/crates/nyaterm-app/assets/icons/ai/new.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/crates/nyaterm-app/assets/icons/ai/search.svg b/crates/nyaterm-app/assets/icons/ai/search.svg index 79c57799..cd9fd53c 100644 --- a/crates/nyaterm-app/assets/icons/ai/search.svg +++ b/crates/nyaterm-app/assets/icons/ai/search.svg @@ -1 +1 @@ - + \ No newline at end of file diff --git a/crates/nyaterm-app/assets/icons/ai/send.svg b/crates/nyaterm-app/assets/icons/ai/send.svg index 87daad06..b6786781 100644 --- a/crates/nyaterm-app/assets/icons/ai/send.svg +++ b/crates/nyaterm-app/assets/icons/ai/send.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/crates/nyaterm-app/assets/icons/ai/settings.svg b/crates/nyaterm-app/assets/icons/ai/settings.svg index bc4c7907..b36758ca 100644 --- a/crates/nyaterm-app/assets/icons/ai/settings.svg +++ b/crates/nyaterm-app/assets/icons/ai/settings.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/crates/nyaterm-app/assets/icons/ai/stop.svg b/crates/nyaterm-app/assets/icons/ai/stop.svg index e41cba83..48d3f2d7 100644 --- a/crates/nyaterm-app/assets/icons/ai/stop.svg +++ b/crates/nyaterm-app/assets/icons/ai/stop.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/crates/nyaterm-app/assets/icons/arrow-down.svg b/crates/nyaterm-app/assets/icons/arrow-down.svg new file mode 100644 index 00000000..b6f2de90 --- /dev/null +++ b/crates/nyaterm-app/assets/icons/arrow-down.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/crates/nyaterm-app/assets/icons/auth.svg b/crates/nyaterm-app/assets/icons/auth.svg index 3a130f71..f1868021 100644 --- a/crates/nyaterm-app/assets/icons/auth.svg +++ b/crates/nyaterm-app/assets/icons/auth.svg @@ -1 +1 @@ - + \ No newline at end of file diff --git a/crates/nyaterm-app/assets/icons/brand/apple.svg b/crates/nyaterm-app/assets/icons/brand/apple.svg new file mode 100644 index 00000000..de4b0d17 --- /dev/null +++ b/crates/nyaterm-app/assets/icons/brand/apple.svg @@ -0,0 +1 @@ +Apple \ No newline at end of file diff --git a/crates/nyaterm-app/assets/icons/brand/aws.svg b/crates/nyaterm-app/assets/icons/brand/aws.svg new file mode 100644 index 00000000..e7483bad --- /dev/null +++ b/crates/nyaterm-app/assets/icons/brand/aws.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/crates/nyaterm-app/assets/icons/brand/baidu.svg b/crates/nyaterm-app/assets/icons/brand/baidu.svg new file mode 100644 index 00000000..d3e65969 --- /dev/null +++ b/crates/nyaterm-app/assets/icons/brand/baidu.svg @@ -0,0 +1 @@ +Baidu \ No newline at end of file diff --git a/crates/nyaterm-app/assets/icons/brand/bilibili.svg b/crates/nyaterm-app/assets/icons/brand/bilibili.svg new file mode 100644 index 00000000..6c114c71 --- /dev/null +++ b/crates/nyaterm-app/assets/icons/brand/bilibili.svg @@ -0,0 +1 @@ +Bilibili \ No newline at end of file diff --git a/crates/nyaterm-app/assets/icons/brand/bing.svg b/crates/nyaterm-app/assets/icons/brand/bing.svg new file mode 100644 index 00000000..b455b9e4 --- /dev/null +++ b/crates/nyaterm-app/assets/icons/brand/bing.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/crates/nyaterm-app/assets/icons/brand/centos.svg b/crates/nyaterm-app/assets/icons/brand/centos.svg new file mode 100644 index 00000000..3f16a9e0 --- /dev/null +++ b/crates/nyaterm-app/assets/icons/brand/centos.svg @@ -0,0 +1 @@ +CentOS \ No newline at end of file diff --git a/crates/nyaterm-app/assets/icons/brand/claude.svg b/crates/nyaterm-app/assets/icons/brand/claude.svg new file mode 100644 index 00000000..1beee861 --- /dev/null +++ b/crates/nyaterm-app/assets/icons/brand/claude.svg @@ -0,0 +1 @@ +Claude \ No newline at end of file diff --git a/crates/nyaterm-app/assets/icons/brand/css.svg b/crates/nyaterm-app/assets/icons/brand/css.svg new file mode 100644 index 00000000..59bb2437 --- /dev/null +++ b/crates/nyaterm-app/assets/icons/brand/css.svg @@ -0,0 +1 @@ +CSS \ No newline at end of file diff --git a/crates/nyaterm-app/assets/icons/brand/debian.svg b/crates/nyaterm-app/assets/icons/brand/debian.svg new file mode 100644 index 00000000..6e4be6a7 --- /dev/null +++ b/crates/nyaterm-app/assets/icons/brand/debian.svg @@ -0,0 +1 @@ +Debian \ No newline at end of file diff --git a/crates/nyaterm-app/assets/icons/brand/docker.svg b/crates/nyaterm-app/assets/icons/brand/docker.svg new file mode 100644 index 00000000..0021a8a7 --- /dev/null +++ b/crates/nyaterm-app/assets/icons/brand/docker.svg @@ -0,0 +1 @@ +Docker \ No newline at end of file diff --git a/crates/nyaterm-app/assets/icons/brand/duckduckgo.svg b/crates/nyaterm-app/assets/icons/brand/duckduckgo.svg new file mode 100644 index 00000000..7b51a80e --- /dev/null +++ b/crates/nyaterm-app/assets/icons/brand/duckduckgo.svg @@ -0,0 +1 @@ +DuckDuckGo \ No newline at end of file diff --git a/crates/nyaterm-app/assets/icons/brand/fedora.svg b/crates/nyaterm-app/assets/icons/brand/fedora.svg new file mode 100644 index 00000000..7f461d0e --- /dev/null +++ b/crates/nyaterm-app/assets/icons/brand/fedora.svg @@ -0,0 +1 @@ +Fedora \ No newline at end of file diff --git a/crates/nyaterm-app/assets/icons/brand/gemini.svg b/crates/nyaterm-app/assets/icons/brand/gemini.svg new file mode 100644 index 00000000..765adb08 --- /dev/null +++ b/crates/nyaterm-app/assets/icons/brand/gemini.svg @@ -0,0 +1 @@ +Google Gemini \ No newline at end of file diff --git a/crates/nyaterm-app/assets/icons/brand/github.svg b/crates/nyaterm-app/assets/icons/brand/github.svg new file mode 100644 index 00000000..538ec5bf --- /dev/null +++ b/crates/nyaterm-app/assets/icons/brand/github.svg @@ -0,0 +1 @@ +GitHub \ No newline at end of file diff --git a/crates/nyaterm-app/assets/icons/brand/gitlab.svg b/crates/nyaterm-app/assets/icons/brand/gitlab.svg new file mode 100644 index 00000000..e4a29630 --- /dev/null +++ b/crates/nyaterm-app/assets/icons/brand/gitlab.svg @@ -0,0 +1 @@ +GitLab \ No newline at end of file diff --git a/crates/nyaterm-app/assets/icons/brand/go.svg b/crates/nyaterm-app/assets/icons/brand/go.svg new file mode 100644 index 00000000..0cadd56b --- /dev/null +++ b/crates/nyaterm-app/assets/icons/brand/go.svg @@ -0,0 +1 @@ +Go \ No newline at end of file diff --git a/crates/nyaterm-app/assets/icons/brand/google.svg b/crates/nyaterm-app/assets/icons/brand/google.svg new file mode 100644 index 00000000..2eaf9155 --- /dev/null +++ b/crates/nyaterm-app/assets/icons/brand/google.svg @@ -0,0 +1 @@ +Google \ No newline at end of file diff --git a/crates/nyaterm-app/assets/icons/brand/googlecloud.svg b/crates/nyaterm-app/assets/icons/brand/googlecloud.svg new file mode 100644 index 00000000..b7b4dcc0 --- /dev/null +++ b/crates/nyaterm-app/assets/icons/brand/googlecloud.svg @@ -0,0 +1 @@ +Google Cloud \ No newline at end of file diff --git a/crates/nyaterm-app/assets/icons/brand/html5.svg b/crates/nyaterm-app/assets/icons/brand/html5.svg new file mode 100644 index 00000000..e8b1e371 --- /dev/null +++ b/crates/nyaterm-app/assets/icons/brand/html5.svg @@ -0,0 +1 @@ +HTML5 \ No newline at end of file diff --git a/crates/nyaterm-app/assets/icons/brand/javascript.svg b/crates/nyaterm-app/assets/icons/brand/javascript.svg new file mode 100644 index 00000000..91e51623 --- /dev/null +++ b/crates/nyaterm-app/assets/icons/brand/javascript.svg @@ -0,0 +1 @@ +JavaScript \ No newline at end of file diff --git a/crates/nyaterm-app/assets/icons/brand/kubernetes.svg b/crates/nyaterm-app/assets/icons/brand/kubernetes.svg new file mode 100644 index 00000000..667f2d47 --- /dev/null +++ b/crates/nyaterm-app/assets/icons/brand/kubernetes.svg @@ -0,0 +1 @@ +Kubernetes \ No newline at end of file diff --git a/crates/nyaterm-app/assets/icons/brand/linux.svg b/crates/nyaterm-app/assets/icons/brand/linux.svg new file mode 100644 index 00000000..381d3d8a --- /dev/null +++ b/crates/nyaterm-app/assets/icons/brand/linux.svg @@ -0,0 +1 @@ +Linux \ No newline at end of file diff --git a/crates/nyaterm-app/assets/icons/brand/mongodb.svg b/crates/nyaterm-app/assets/icons/brand/mongodb.svg new file mode 100644 index 00000000..a4c81748 --- /dev/null +++ b/crates/nyaterm-app/assets/icons/brand/mongodb.svg @@ -0,0 +1 @@ +MongoDB \ No newline at end of file diff --git a/crates/nyaterm-app/assets/icons/brand/mysql.svg b/crates/nyaterm-app/assets/icons/brand/mysql.svg new file mode 100644 index 00000000..195caada --- /dev/null +++ b/crates/nyaterm-app/assets/icons/brand/mysql.svg @@ -0,0 +1 @@ +MySQL \ No newline at end of file diff --git a/crates/nyaterm-app/assets/icons/brand/nginx.svg b/crates/nyaterm-app/assets/icons/brand/nginx.svg new file mode 100644 index 00000000..5875410b --- /dev/null +++ b/crates/nyaterm-app/assets/icons/brand/nginx.svg @@ -0,0 +1 @@ +NGINX \ No newline at end of file diff --git a/crates/nyaterm-app/assets/icons/brand/nodedotjs.svg b/crates/nyaterm-app/assets/icons/brand/nodedotjs.svg new file mode 100644 index 00000000..281c8296 --- /dev/null +++ b/crates/nyaterm-app/assets/icons/brand/nodedotjs.svg @@ -0,0 +1 @@ +Node.js \ No newline at end of file diff --git a/crates/nyaterm-app/assets/icons/brand/openai.svg b/crates/nyaterm-app/assets/icons/brand/openai.svg new file mode 100644 index 00000000..384e411c --- /dev/null +++ b/crates/nyaterm-app/assets/icons/brand/openai.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/crates/nyaterm-app/assets/icons/brand/php.svg b/crates/nyaterm-app/assets/icons/brand/php.svg new file mode 100644 index 00000000..a08156af --- /dev/null +++ b/crates/nyaterm-app/assets/icons/brand/php.svg @@ -0,0 +1 @@ +PHP \ No newline at end of file diff --git a/crates/nyaterm-app/assets/icons/brand/postgresql.svg b/crates/nyaterm-app/assets/icons/brand/postgresql.svg new file mode 100644 index 00000000..dcf75b72 --- /dev/null +++ b/crates/nyaterm-app/assets/icons/brand/postgresql.svg @@ -0,0 +1 @@ +PostgreSQL \ No newline at end of file diff --git a/crates/nyaterm-app/assets/icons/brand/python.svg b/crates/nyaterm-app/assets/icons/brand/python.svg new file mode 100644 index 00000000..30587d81 --- /dev/null +++ b/crates/nyaterm-app/assets/icons/brand/python.svg @@ -0,0 +1 @@ +Python \ No newline at end of file diff --git a/crates/nyaterm-app/assets/icons/brand/redis.svg b/crates/nyaterm-app/assets/icons/brand/redis.svg new file mode 100644 index 00000000..fc47db86 --- /dev/null +++ b/crates/nyaterm-app/assets/icons/brand/redis.svg @@ -0,0 +1 @@ +Redis \ No newline at end of file diff --git a/crates/nyaterm-app/assets/icons/brand/rust.svg b/crates/nyaterm-app/assets/icons/brand/rust.svg new file mode 100644 index 00000000..b95ce42a --- /dev/null +++ b/crates/nyaterm-app/assets/icons/brand/rust.svg @@ -0,0 +1 @@ +Rust \ No newline at end of file diff --git a/crates/nyaterm-app/assets/icons/brand/server.svg b/crates/nyaterm-app/assets/icons/brand/server.svg new file mode 100644 index 00000000..2e93fefa --- /dev/null +++ b/crates/nyaterm-app/assets/icons/brand/server.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/crates/nyaterm-app/assets/icons/brand/typescript.svg b/crates/nyaterm-app/assets/icons/brand/typescript.svg new file mode 100644 index 00000000..e8beb740 --- /dev/null +++ b/crates/nyaterm-app/assets/icons/brand/typescript.svg @@ -0,0 +1 @@ +TypeScript \ No newline at end of file diff --git a/crates/nyaterm-app/assets/icons/brand/ubuntu.svg b/crates/nyaterm-app/assets/icons/brand/ubuntu.svg new file mode 100644 index 00000000..227f86aa --- /dev/null +++ b/crates/nyaterm-app/assets/icons/brand/ubuntu.svg @@ -0,0 +1 @@ +Ubuntu \ No newline at end of file diff --git a/crates/nyaterm-app/assets/icons/brand/yahoo.svg b/crates/nyaterm-app/assets/icons/brand/yahoo.svg new file mode 100644 index 00000000..c164caac --- /dev/null +++ b/crates/nyaterm-app/assets/icons/brand/yahoo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/crates/nyaterm-app/assets/icons/brand/youtube.svg b/crates/nyaterm-app/assets/icons/brand/youtube.svg new file mode 100644 index 00000000..0492366a --- /dev/null +++ b/crates/nyaterm-app/assets/icons/brand/youtube.svg @@ -0,0 +1 @@ +YouTube \ No newline at end of file diff --git a/crates/nyaterm-app/assets/icons/brand/zhihu.svg b/crates/nyaterm-app/assets/icons/brand/zhihu.svg new file mode 100644 index 00000000..c283d23e --- /dev/null +++ b/crates/nyaterm-app/assets/icons/brand/zhihu.svg @@ -0,0 +1 @@ +Zhihu \ No newline at end of file diff --git a/crates/nyaterm-app/assets/icons/check.svg b/crates/nyaterm-app/assets/icons/check.svg index 43dd6880..8676ca39 100644 --- a/crates/nyaterm-app/assets/icons/check.svg +++ b/crates/nyaterm-app/assets/icons/check.svg @@ -1 +1 @@ - + \ No newline at end of file diff --git a/crates/nyaterm-app/assets/icons/chevron-down.svg b/crates/nyaterm-app/assets/icons/chevron-down.svg index 4fe8fcf9..4428af00 100644 --- a/crates/nyaterm-app/assets/icons/chevron-down.svg +++ b/crates/nyaterm-app/assets/icons/chevron-down.svg @@ -1 +1 @@ - + \ No newline at end of file diff --git a/crates/nyaterm-app/assets/icons/chevron-up.svg b/crates/nyaterm-app/assets/icons/chevron-up.svg index ce0e5a81..53985246 100644 --- a/crates/nyaterm-app/assets/icons/chevron-up.svg +++ b/crates/nyaterm-app/assets/icons/chevron-up.svg @@ -1 +1 @@ - + \ No newline at end of file diff --git a/crates/nyaterm-app/assets/icons/close.svg b/crates/nyaterm-app/assets/icons/close.svg new file mode 100644 index 00000000..c3f7c2fd --- /dev/null +++ b/crates/nyaterm-app/assets/icons/close.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/crates/nyaterm-app/assets/icons/commands.svg b/crates/nyaterm-app/assets/icons/commands.svg index 00a23a3c..d5fa3689 100644 --- a/crates/nyaterm-app/assets/icons/commands.svg +++ b/crates/nyaterm-app/assets/icons/commands.svg @@ -1 +1 @@ - + \ No newline at end of file diff --git a/crates/nyaterm-app/assets/icons/conn/add.svg b/crates/nyaterm-app/assets/icons/conn/add.svg index 09320265..58f73e96 100644 --- a/crates/nyaterm-app/assets/icons/conn/add.svg +++ b/crates/nyaterm-app/assets/icons/conn/add.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/crates/nyaterm-app/assets/icons/conn/apple.svg b/crates/nyaterm-app/assets/icons/conn/apple.svg deleted file mode 100644 index 2c51fd86..00000000 --- a/crates/nyaterm-app/assets/icons/conn/apple.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/crates/nyaterm-app/assets/icons/conn/connect.svg b/crates/nyaterm-app/assets/icons/conn/connect.svg index 592108ac..f39e2a7e 100644 --- a/crates/nyaterm-app/assets/icons/conn/connect.svg +++ b/crates/nyaterm-app/assets/icons/conn/connect.svg @@ -1 +1 @@ - + \ No newline at end of file diff --git a/crates/nyaterm-app/assets/icons/conn/debian.svg b/crates/nyaterm-app/assets/icons/conn/debian.svg deleted file mode 100644 index d0913060..00000000 --- a/crates/nyaterm-app/assets/icons/conn/debian.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/crates/nyaterm-app/assets/icons/conn/docker.svg b/crates/nyaterm-app/assets/icons/conn/docker.svg deleted file mode 100644 index bfb5c87c..00000000 --- a/crates/nyaterm-app/assets/icons/conn/docker.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/crates/nyaterm-app/assets/icons/conn/file.svg b/crates/nyaterm-app/assets/icons/conn/file.svg index e125bd99..6aa287e0 100644 --- a/crates/nyaterm-app/assets/icons/conn/file.svg +++ b/crates/nyaterm-app/assets/icons/conn/file.svg @@ -1 +1 @@ - + \ No newline at end of file diff --git a/crates/nyaterm-app/assets/icons/conn/flash.svg b/crates/nyaterm-app/assets/icons/conn/flash.svg index 34888f0f..e948043d 100644 --- a/crates/nyaterm-app/assets/icons/conn/flash.svg +++ b/crates/nyaterm-app/assets/icons/conn/flash.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/crates/nyaterm-app/assets/icons/conn/folder.svg b/crates/nyaterm-app/assets/icons/conn/folder.svg index 7b7bf550..e70b01ce 100644 --- a/crates/nyaterm-app/assets/icons/conn/folder.svg +++ b/crates/nyaterm-app/assets/icons/conn/folder.svg @@ -1 +1 @@ - + \ No newline at end of file diff --git a/crates/nyaterm-app/assets/icons/conn/github.svg b/crates/nyaterm-app/assets/icons/conn/github.svg deleted file mode 100644 index 0bf89b00..00000000 --- a/crates/nyaterm-app/assets/icons/conn/github.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/crates/nyaterm-app/assets/icons/conn/linux.svg b/crates/nyaterm-app/assets/icons/conn/linux.svg deleted file mode 100644 index 058ed03d..00000000 --- a/crates/nyaterm-app/assets/icons/conn/linux.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/crates/nyaterm-app/assets/icons/conn/more.svg b/crates/nyaterm-app/assets/icons/conn/more.svg index 88540e69..f2427f84 100644 --- a/crates/nyaterm-app/assets/icons/conn/more.svg +++ b/crates/nyaterm-app/assets/icons/conn/more.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/crates/nyaterm-app/assets/icons/conn/python.svg b/crates/nyaterm-app/assets/icons/conn/python.svg deleted file mode 100644 index f2d68641..00000000 --- a/crates/nyaterm-app/assets/icons/conn/python.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/crates/nyaterm-app/assets/icons/conn/serial.svg b/crates/nyaterm-app/assets/icons/conn/serial.svg index 2b3902ca..f4c8e262 100644 --- a/crates/nyaterm-app/assets/icons/conn/serial.svg +++ b/crates/nyaterm-app/assets/icons/conn/serial.svg @@ -1 +1 @@ - + \ No newline at end of file diff --git a/crates/nyaterm-app/assets/icons/conn/server.svg b/crates/nyaterm-app/assets/icons/conn/server.svg index 31b9ea10..4079ced5 100644 --- a/crates/nyaterm-app/assets/icons/conn/server.svg +++ b/crates/nyaterm-app/assets/icons/conn/server.svg @@ -1 +1 @@ - + \ No newline at end of file diff --git a/crates/nyaterm-app/assets/icons/conn/sort.svg b/crates/nyaterm-app/assets/icons/conn/sort.svg index ebeddf98..84efa535 100644 --- a/crates/nyaterm-app/assets/icons/conn/sort.svg +++ b/crates/nyaterm-app/assets/icons/conn/sort.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/crates/nyaterm-app/assets/icons/conn/symlink.svg b/crates/nyaterm-app/assets/icons/conn/symlink.svg index 9497b0eb..f39e2a7e 100644 --- a/crates/nyaterm-app/assets/icons/conn/symlink.svg +++ b/crates/nyaterm-app/assets/icons/conn/symlink.svg @@ -1 +1 @@ - + \ No newline at end of file diff --git a/crates/nyaterm-app/assets/icons/conn/telnet.svg b/crates/nyaterm-app/assets/icons/conn/telnet.svg index f9f4e4d7..4079ced5 100644 --- a/crates/nyaterm-app/assets/icons/conn/telnet.svg +++ b/crates/nyaterm-app/assets/icons/conn/telnet.svg @@ -1 +1 @@ - + \ No newline at end of file diff --git a/crates/nyaterm-app/assets/icons/conn/terminal.svg b/crates/nyaterm-app/assets/icons/conn/terminal.svg index d99912f3..b1a4d635 100644 --- a/crates/nyaterm-app/assets/icons/conn/terminal.svg +++ b/crates/nyaterm-app/assets/icons/conn/terminal.svg @@ -1 +1 @@ - + \ No newline at end of file diff --git a/crates/nyaterm-app/assets/icons/conn/ubuntu.svg b/crates/nyaterm-app/assets/icons/conn/ubuntu.svg deleted file mode 100644 index 880f1322..00000000 --- a/crates/nyaterm-app/assets/icons/conn/ubuntu.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/crates/nyaterm-app/assets/icons/conn/windows.svg b/crates/nyaterm-app/assets/icons/conn/windows.svg deleted file mode 100644 index da974cd8..00000000 --- a/crates/nyaterm-app/assets/icons/conn/windows.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/crates/nyaterm-app/assets/icons/connections.svg b/crates/nyaterm-app/assets/icons/connections.svg index 31b9ea10..4079ced5 100644 --- a/crates/nyaterm-app/assets/icons/connections.svg +++ b/crates/nyaterm-app/assets/icons/connections.svg @@ -1 +1 @@ - + \ No newline at end of file diff --git a/crates/nyaterm-app/assets/icons/copy.svg b/crates/nyaterm-app/assets/icons/copy.svg index a26c38fe..7c6b60ae 100644 --- a/crates/nyaterm-app/assets/icons/copy.svg +++ b/crates/nyaterm-app/assets/icons/copy.svg @@ -1 +1 @@ - + \ No newline at end of file diff --git a/crates/nyaterm-app/assets/icons/docker.svg b/crates/nyaterm-app/assets/icons/docker.svg index 89e50601..0021a8a7 100644 --- a/crates/nyaterm-app/assets/icons/docker.svg +++ b/crates/nyaterm-app/assets/icons/docker.svg @@ -1 +1 @@ - +Docker \ No newline at end of file diff --git a/crates/nyaterm-app/assets/icons/eye.svg b/crates/nyaterm-app/assets/icons/eye.svg index 99b75c4c..5c700e39 100644 --- a/crates/nyaterm-app/assets/icons/eye.svg +++ b/crates/nyaterm-app/assets/icons/eye.svg @@ -1 +1 @@ - + \ No newline at end of file diff --git a/crates/nyaterm-app/assets/icons/fe/back.svg b/crates/nyaterm-app/assets/icons/fe/back.svg index d670bfbe..e119372c 100644 --- a/crates/nyaterm-app/assets/icons/fe/back.svg +++ b/crates/nyaterm-app/assets/icons/fe/back.svg @@ -1 +1 @@ - + \ No newline at end of file diff --git a/crates/nyaterm-app/assets/icons/fe/bookmark-remove.svg b/crates/nyaterm-app/assets/icons/fe/bookmark-remove.svg index 6d4730c9..679a1aac 100644 --- a/crates/nyaterm-app/assets/icons/fe/bookmark-remove.svg +++ b/crates/nyaterm-app/assets/icons/fe/bookmark-remove.svg @@ -1 +1 @@ - + \ No newline at end of file diff --git a/crates/nyaterm-app/assets/icons/fe/delete.svg b/crates/nyaterm-app/assets/icons/fe/delete.svg index ae9c131a..0969a4dc 100644 --- a/crates/nyaterm-app/assets/icons/fe/delete.svg +++ b/crates/nyaterm-app/assets/icons/fe/delete.svg @@ -1 +1 @@ - + \ No newline at end of file diff --git a/crates/nyaterm-app/assets/icons/fe/download.svg b/crates/nyaterm-app/assets/icons/fe/download.svg index ebd0850e..3b44aec9 100644 --- a/crates/nyaterm-app/assets/icons/fe/download.svg +++ b/crates/nyaterm-app/assets/icons/fe/download.svg @@ -1 +1 @@ - + \ No newline at end of file diff --git a/crates/nyaterm-app/assets/icons/fe/forward.svg b/crates/nyaterm-app/assets/icons/fe/forward.svg index ce5487b4..aae64cf7 100644 --- a/crates/nyaterm-app/assets/icons/fe/forward.svg +++ b/crates/nyaterm-app/assets/icons/fe/forward.svg @@ -1 +1 @@ - + \ No newline at end of file diff --git a/crates/nyaterm-app/assets/icons/fe/new-file.svg b/crates/nyaterm-app/assets/icons/fe/new-file.svg index 40fc9b88..36c9d08b 100644 --- a/crates/nyaterm-app/assets/icons/fe/new-file.svg +++ b/crates/nyaterm-app/assets/icons/fe/new-file.svg @@ -1 +1 @@ - + \ No newline at end of file diff --git a/crates/nyaterm-app/assets/icons/fe/new-folder.svg b/crates/nyaterm-app/assets/icons/fe/new-folder.svg index b353e0a7..3c9da3d6 100644 --- a/crates/nyaterm-app/assets/icons/fe/new-folder.svg +++ b/crates/nyaterm-app/assets/icons/fe/new-folder.svg @@ -1 +1 @@ - + \ No newline at end of file diff --git a/crates/nyaterm-app/assets/icons/fe/paste.svg b/crates/nyaterm-app/assets/icons/fe/paste.svg index c44ecc41..af63a642 100644 --- a/crates/nyaterm-app/assets/icons/fe/paste.svg +++ b/crates/nyaterm-app/assets/icons/fe/paste.svg @@ -1 +1 @@ - + \ No newline at end of file diff --git a/crates/nyaterm-app/assets/icons/fe/refresh.svg b/crates/nyaterm-app/assets/icons/fe/refresh.svg index 9171dfce..217df885 100644 --- a/crates/nyaterm-app/assets/icons/fe/refresh.svg +++ b/crates/nyaterm-app/assets/icons/fe/refresh.svg @@ -1 +1 @@ - + \ No newline at end of file diff --git a/crates/nyaterm-app/assets/icons/fe/search.svg b/crates/nyaterm-app/assets/icons/fe/search.svg index 79c57799..cd9fd53c 100644 --- a/crates/nyaterm-app/assets/icons/fe/search.svg +++ b/crates/nyaterm-app/assets/icons/fe/search.svg @@ -1 +1 @@ - + \ No newline at end of file diff --git a/crates/nyaterm-app/assets/icons/fe/star-outline.svg b/crates/nyaterm-app/assets/icons/fe/star-outline.svg index df1b0951..edef6948 100644 --- a/crates/nyaterm-app/assets/icons/fe/star-outline.svg +++ b/crates/nyaterm-app/assets/icons/fe/star-outline.svg @@ -1 +1 @@ - + \ No newline at end of file diff --git a/crates/nyaterm-app/assets/icons/fe/star.svg b/crates/nyaterm-app/assets/icons/fe/star.svg index b270983d..10a5d7d4 100644 --- a/crates/nyaterm-app/assets/icons/fe/star.svg +++ b/crates/nyaterm-app/assets/icons/fe/star.svg @@ -1 +1 @@ - + \ No newline at end of file diff --git a/crates/nyaterm-app/assets/icons/fe/sync.svg b/crates/nyaterm-app/assets/icons/fe/sync.svg index 164f1392..65004930 100644 --- a/crates/nyaterm-app/assets/icons/fe/sync.svg +++ b/crates/nyaterm-app/assets/icons/fe/sync.svg @@ -1 +1 @@ - + \ No newline at end of file diff --git a/crates/nyaterm-app/assets/icons/fe/up.svg b/crates/nyaterm-app/assets/icons/fe/up.svg index 755a7fab..b84aab84 100644 --- a/crates/nyaterm-app/assets/icons/fe/up.svg +++ b/crates/nyaterm-app/assets/icons/fe/up.svg @@ -1 +1 @@ - + \ No newline at end of file diff --git a/crates/nyaterm-app/assets/icons/fe/upload-folder.svg b/crates/nyaterm-app/assets/icons/fe/upload-folder.svg index bc99af7e..3b85ccbe 100644 --- a/crates/nyaterm-app/assets/icons/fe/upload-folder.svg +++ b/crates/nyaterm-app/assets/icons/fe/upload-folder.svg @@ -1 +1 @@ - + \ No newline at end of file diff --git a/crates/nyaterm-app/assets/icons/fe/upload.svg b/crates/nyaterm-app/assets/icons/fe/upload.svg index c135f963..76168acf 100644 --- a/crates/nyaterm-app/assets/icons/fe/upload.svg +++ b/crates/nyaterm-app/assets/icons/fe/upload.svg @@ -1 +1 @@ - + \ No newline at end of file diff --git a/crates/nyaterm-app/assets/icons/file/apps.svg b/crates/nyaterm-app/assets/icons/file/apps.svg new file mode 100644 index 00000000..b0ccb6c8 --- /dev/null +++ b/crates/nyaterm-app/assets/icons/file/apps.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/crates/nyaterm-app/assets/icons/file/archive.svg b/crates/nyaterm-app/assets/icons/file/archive.svg new file mode 100644 index 00000000..1fa6401d --- /dev/null +++ b/crates/nyaterm-app/assets/icons/file/archive.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/crates/nyaterm-app/assets/icons/file/audio.svg b/crates/nyaterm-app/assets/icons/file/audio.svg new file mode 100644 index 00000000..08ad00de --- /dev/null +++ b/crates/nyaterm-app/assets/icons/file/audio.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/crates/nyaterm-app/assets/icons/file/code.svg b/crates/nyaterm-app/assets/icons/file/code.svg new file mode 100644 index 00000000..01454236 --- /dev/null +++ b/crates/nyaterm-app/assets/icons/file/code.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/crates/nyaterm-app/assets/icons/file/data-object.svg b/crates/nyaterm-app/assets/icons/file/data-object.svg new file mode 100644 index 00000000..8199c504 --- /dev/null +++ b/crates/nyaterm-app/assets/icons/file/data-object.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/crates/nyaterm-app/assets/icons/file/description.svg b/crates/nyaterm-app/assets/icons/file/description.svg new file mode 100644 index 00000000..1585a6ca --- /dev/null +++ b/crates/nyaterm-app/assets/icons/file/description.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/crates/nyaterm-app/assets/icons/file/image.svg b/crates/nyaterm-app/assets/icons/file/image.svg new file mode 100644 index 00000000..6ccc16ad --- /dev/null +++ b/crates/nyaterm-app/assets/icons/file/image.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/crates/nyaterm-app/assets/icons/file/movie.svg b/crates/nyaterm-app/assets/icons/file/movie.svg new file mode 100644 index 00000000..632ecd4e --- /dev/null +++ b/crates/nyaterm-app/assets/icons/file/movie.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/crates/nyaterm-app/assets/icons/file/pdf.svg b/crates/nyaterm-app/assets/icons/file/pdf.svg new file mode 100644 index 00000000..e50f55f5 --- /dev/null +++ b/crates/nyaterm-app/assets/icons/file/pdf.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/crates/nyaterm-app/assets/icons/file/present.svg b/crates/nyaterm-app/assets/icons/file/present.svg new file mode 100644 index 00000000..27c21d79 --- /dev/null +++ b/crates/nyaterm-app/assets/icons/file/present.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/crates/nyaterm-app/assets/icons/file/storage.svg b/crates/nyaterm-app/assets/icons/file/storage.svg new file mode 100644 index 00000000..8cae0c1e --- /dev/null +++ b/crates/nyaterm-app/assets/icons/file/storage.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/crates/nyaterm-app/assets/icons/file/table.svg b/crates/nyaterm-app/assets/icons/file/table.svg new file mode 100644 index 00000000..c6491c7b --- /dev/null +++ b/crates/nyaterm-app/assets/icons/file/table.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/crates/nyaterm-app/assets/icons/file/text.svg b/crates/nyaterm-app/assets/icons/file/text.svg new file mode 100644 index 00000000..55c1c072 --- /dev/null +++ b/crates/nyaterm-app/assets/icons/file/text.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/crates/nyaterm-app/assets/icons/files.svg b/crates/nyaterm-app/assets/icons/files.svg index fbe06236..931189a4 100644 --- a/crates/nyaterm-app/assets/icons/files.svg +++ b/crates/nyaterm-app/assets/icons/files.svg @@ -1 +1 @@ - + \ No newline at end of file diff --git a/crates/nyaterm-app/assets/icons/history.svg b/crates/nyaterm-app/assets/icons/history.svg index dc9f8c7f..f8f018c8 100644 --- a/crates/nyaterm-app/assets/icons/history.svg +++ b/crates/nyaterm-app/assets/icons/history.svg @@ -1 +1 @@ - + \ No newline at end of file diff --git a/crates/nyaterm-app/assets/icons/import.svg b/crates/nyaterm-app/assets/icons/import.svg index 3fc78350..ef421c32 100644 --- a/crates/nyaterm-app/assets/icons/import.svg +++ b/crates/nyaterm-app/assets/icons/import.svg @@ -1 +1 @@ - + \ No newline at end of file diff --git a/crates/nyaterm-app/assets/icons/import/mobaxterm.svg b/crates/nyaterm-app/assets/icons/import/mobaxterm.svg deleted file mode 100644 index e58f0b01..00000000 --- a/crates/nyaterm-app/assets/icons/import/mobaxterm.svg +++ /dev/null @@ -1,27 +0,0 @@ - - - - diff --git a/crates/nyaterm-app/assets/icons/import/windterm.svg b/crates/nyaterm-app/assets/icons/import/windterm.svg deleted file mode 100644 index b226356f..00000000 --- a/crates/nyaterm-app/assets/icons/import/windterm.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - diff --git a/crates/nyaterm-app/assets/icons/import/xshell.svg b/crates/nyaterm-app/assets/icons/import/xshell.svg deleted file mode 100644 index 458747fa..00000000 --- a/crates/nyaterm-app/assets/icons/import/xshell.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - diff --git a/crates/nyaterm-app/assets/icons/lock.svg b/crates/nyaterm-app/assets/icons/lock.svg index aa507a55..2154a2c5 100644 --- a/crates/nyaterm-app/assets/icons/lock.svg +++ b/crates/nyaterm-app/assets/icons/lock.svg @@ -1 +1 @@ - + \ No newline at end of file diff --git a/crates/nyaterm-app/assets/icons/menu/article.svg b/crates/nyaterm-app/assets/icons/menu/article.svg index ebd8523a..55c1c072 100644 --- a/crates/nyaterm-app/assets/icons/menu/article.svg +++ b/crates/nyaterm-app/assets/icons/menu/article.svg @@ -1 +1 @@ - + \ No newline at end of file diff --git a/crates/nyaterm-app/assets/icons/menu/book.svg b/crates/nyaterm-app/assets/icons/menu/book.svg index a93fa084..d238211b 100644 --- a/crates/nyaterm-app/assets/icons/menu/book.svg +++ b/crates/nyaterm-app/assets/icons/menu/book.svg @@ -1 +1 @@ - + \ No newline at end of file diff --git a/crates/nyaterm-app/assets/icons/menu/broadcast.svg b/crates/nyaterm-app/assets/icons/menu/broadcast.svg index 7b7938f2..f4c8e262 100644 --- a/crates/nyaterm-app/assets/icons/menu/broadcast.svg +++ b/crates/nyaterm-app/assets/icons/menu/broadcast.svg @@ -1 +1 @@ - + \ No newline at end of file diff --git a/crates/nyaterm-app/assets/icons/menu/export.svg b/crates/nyaterm-app/assets/icons/menu/export.svg index bdf5bfd8..0fe9aadd 100644 --- a/crates/nyaterm-app/assets/icons/menu/export.svg +++ b/crates/nyaterm-app/assets/icons/menu/export.svg @@ -1 +1 @@ - + \ No newline at end of file diff --git a/crates/nyaterm-app/assets/icons/menu/fit.svg b/crates/nyaterm-app/assets/icons/menu/fit.svg index 82631c3e..e8a07e71 100644 --- a/crates/nyaterm-app/assets/icons/menu/fit.svg +++ b/crates/nyaterm-app/assets/icons/menu/fit.svg @@ -1 +1 @@ - + \ No newline at end of file diff --git a/crates/nyaterm-app/assets/icons/menu/horizontal.svg b/crates/nyaterm-app/assets/icons/menu/horizontal.svg index 1c9fcd3f..4b7ef90b 100644 --- a/crates/nyaterm-app/assets/icons/menu/horizontal.svg +++ b/crates/nyaterm-app/assets/icons/menu/horizontal.svg @@ -1 +1 @@ - + \ No newline at end of file diff --git a/crates/nyaterm-app/assets/icons/menu/info.svg b/crates/nyaterm-app/assets/icons/menu/info.svg index 1e8d33a2..22ef137e 100644 --- a/crates/nyaterm-app/assets/icons/menu/info.svg +++ b/crates/nyaterm-app/assets/icons/menu/info.svg @@ -1 +1 @@ - + \ No newline at end of file diff --git a/crates/nyaterm-app/assets/icons/menu/menu.svg b/crates/nyaterm-app/assets/icons/menu/menu.svg index 667c03f4..ae7c36cb 100644 --- a/crates/nyaterm-app/assets/icons/menu/menu.svg +++ b/crates/nyaterm-app/assets/icons/menu/menu.svg @@ -1 +1 @@ - + \ No newline at end of file diff --git a/crates/nyaterm-app/assets/icons/menu/palette.svg b/crates/nyaterm-app/assets/icons/menu/palette.svg index dc800451..a4be942f 100644 --- a/crates/nyaterm-app/assets/icons/menu/palette.svg +++ b/crates/nyaterm-app/assets/icons/menu/palette.svg @@ -1 +1 @@ - + \ No newline at end of file diff --git a/crates/nyaterm-app/assets/icons/menu/reset.svg b/crates/nyaterm-app/assets/icons/menu/reset.svg index 5fb8c790..6c59c4da 100644 --- a/crates/nyaterm-app/assets/icons/menu/reset.svg +++ b/crates/nyaterm-app/assets/icons/menu/reset.svg @@ -1 +1 @@ - + \ No newline at end of file diff --git a/crates/nyaterm-app/assets/icons/menu/sidebar.svg b/crates/nyaterm-app/assets/icons/menu/sidebar.svg index 6b2b520c..f11e08a7 100644 --- a/crates/nyaterm-app/assets/icons/menu/sidebar.svg +++ b/crates/nyaterm-app/assets/icons/menu/sidebar.svg @@ -1 +1 @@ - + \ No newline at end of file diff --git a/crates/nyaterm-app/assets/icons/menu/split.svg b/crates/nyaterm-app/assets/icons/menu/split.svg index aa6ac560..60d15a00 100644 --- a/crates/nyaterm-app/assets/icons/menu/split.svg +++ b/crates/nyaterm-app/assets/icons/menu/split.svg @@ -1 +1 @@ - + \ No newline at end of file diff --git a/crates/nyaterm-app/assets/icons/menu/update.svg b/crates/nyaterm-app/assets/icons/menu/update.svg index 5b181e53..2e02f709 100644 --- a/crates/nyaterm-app/assets/icons/menu/update.svg +++ b/crates/nyaterm-app/assets/icons/menu/update.svg @@ -1 +1 @@ - + \ No newline at end of file diff --git a/crates/nyaterm-app/assets/icons/menu/vertical.svg b/crates/nyaterm-app/assets/icons/menu/vertical.svg index 4b704390..2e63c3c0 100644 --- a/crates/nyaterm-app/assets/icons/menu/vertical.svg +++ b/crates/nyaterm-app/assets/icons/menu/vertical.svg @@ -1 +1 @@ - + \ No newline at end of file diff --git a/crates/nyaterm-app/assets/icons/menu/zoom-in.svg b/crates/nyaterm-app/assets/icons/menu/zoom-in.svg index 47ded04b..0c257ee8 100644 --- a/crates/nyaterm-app/assets/icons/menu/zoom-in.svg +++ b/crates/nyaterm-app/assets/icons/menu/zoom-in.svg @@ -1 +1 @@ - + \ No newline at end of file diff --git a/crates/nyaterm-app/assets/icons/menu/zoom-out.svg b/crates/nyaterm-app/assets/icons/menu/zoom-out.svg index 4ec63ec4..b511470c 100644 --- a/crates/nyaterm-app/assets/icons/menu/zoom-out.svg +++ b/crates/nyaterm-app/assets/icons/menu/zoom-out.svg @@ -1 +1 @@ - + \ No newline at end of file diff --git a/crates/nyaterm-app/assets/icons/migration.svg b/crates/nyaterm-app/assets/icons/migration.svg index 3df9aabd..75333ca5 100644 --- a/crates/nyaterm-app/assets/icons/migration.svg +++ b/crates/nyaterm-app/assets/icons/migration.svg @@ -1 +1 @@ - + \ No newline at end of file diff --git a/crates/nyaterm-app/assets/icons/net/delete.svg b/crates/nyaterm-app/assets/icons/net/delete.svg index ae9c131a..0969a4dc 100644 --- a/crates/nyaterm-app/assets/icons/net/delete.svg +++ b/crates/nyaterm-app/assets/icons/net/delete.svg @@ -1 +1 @@ - + \ No newline at end of file diff --git a/crates/nyaterm-app/assets/icons/net/edit.svg b/crates/nyaterm-app/assets/icons/net/edit.svg index 85316d93..a2841f94 100644 --- a/crates/nyaterm-app/assets/icons/net/edit.svg +++ b/crates/nyaterm-app/assets/icons/net/edit.svg @@ -1 +1 @@ - + \ No newline at end of file diff --git a/crates/nyaterm-app/assets/icons/net/move.svg b/crates/nyaterm-app/assets/icons/net/move.svg index c9887b74..11bcc0e1 100644 --- a/crates/nyaterm-app/assets/icons/net/move.svg +++ b/crates/nyaterm-app/assets/icons/net/move.svg @@ -1 +1 @@ - + \ No newline at end of file diff --git a/crates/nyaterm-app/assets/icons/network.svg b/crates/nyaterm-app/assets/icons/network.svg index d5eee3aa..55c6883b 100644 --- a/crates/nyaterm-app/assets/icons/network.svg +++ b/crates/nyaterm-app/assets/icons/network.svg @@ -1 +1 @@ - + \ No newline at end of file diff --git a/crates/nyaterm-app/assets/icons/pin.svg b/crates/nyaterm-app/assets/icons/pin.svg index f12ed5d4..568580e1 100644 --- a/crates/nyaterm-app/assets/icons/pin.svg +++ b/crates/nyaterm-app/assets/icons/pin.svg @@ -1 +1 @@ - + \ No newline at end of file diff --git a/crates/nyaterm-app/assets/icons/processes.svg b/crates/nyaterm-app/assets/icons/processes.svg index c80b387f..86d6ff68 100644 --- a/crates/nyaterm-app/assets/icons/processes.svg +++ b/crates/nyaterm-app/assets/icons/processes.svg @@ -1 +1 @@ - + \ No newline at end of file diff --git a/crates/nyaterm-app/assets/icons/quote.svg b/crates/nyaterm-app/assets/icons/quote.svg index ac2847a8..9d8fa8b4 100644 --- a/crates/nyaterm-app/assets/icons/quote.svg +++ b/crates/nyaterm-app/assets/icons/quote.svg @@ -1 +1 @@ - + \ No newline at end of file diff --git a/crates/nyaterm-app/assets/icons/record.svg b/crates/nyaterm-app/assets/icons/record.svg index 18565709..d91ccfd9 100644 --- a/crates/nyaterm-app/assets/icons/record.svg +++ b/crates/nyaterm-app/assets/icons/record.svg @@ -1 +1 @@ - + \ No newline at end of file diff --git a/crates/nyaterm-app/assets/icons/resources.svg b/crates/nyaterm-app/assets/icons/resources.svg index 2a2478fe..7caf14e7 100644 --- a/crates/nyaterm-app/assets/icons/resources.svg +++ b/crates/nyaterm-app/assets/icons/resources.svg @@ -1 +1 @@ - + \ No newline at end of file diff --git a/crates/nyaterm-app/assets/icons/send.svg b/crates/nyaterm-app/assets/icons/send.svg index d61a6fda..b6786781 100644 --- a/crates/nyaterm-app/assets/icons/send.svg +++ b/crates/nyaterm-app/assets/icons/send.svg @@ -1 +1 @@ - + \ No newline at end of file diff --git a/crates/nyaterm-app/assets/icons/session/disconnect.svg b/crates/nyaterm-app/assets/icons/session/disconnect.svg index a22580a4..0ea35879 100644 --- a/crates/nyaterm-app/assets/icons/session/disconnect.svg +++ b/crates/nyaterm-app/assets/icons/session/disconnect.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/crates/nyaterm-app/assets/icons/session/more.svg b/crates/nyaterm-app/assets/icons/session/more.svg index 86531ca6..afb392c8 100644 --- a/crates/nyaterm-app/assets/icons/session/more.svg +++ b/crates/nyaterm-app/assets/icons/session/more.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/crates/nyaterm-app/assets/icons/session/reconnect.svg b/crates/nyaterm-app/assets/icons/session/reconnect.svg index 8b666792..217df885 100644 --- a/crates/nyaterm-app/assets/icons/session/reconnect.svg +++ b/crates/nyaterm-app/assets/icons/session/reconnect.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/crates/nyaterm-app/assets/icons/session/record.svg b/crates/nyaterm-app/assets/icons/session/record.svg index 23bcb539..d91ccfd9 100644 --- a/crates/nyaterm-app/assets/icons/session/record.svg +++ b/crates/nyaterm-app/assets/icons/session/record.svg @@ -1 +1 @@ - + \ No newline at end of file diff --git a/crates/nyaterm-app/assets/icons/session/rename.svg b/crates/nyaterm-app/assets/icons/session/rename.svg index f64ee8be..a4fa4478 100644 --- a/crates/nyaterm-app/assets/icons/session/rename.svg +++ b/crates/nyaterm-app/assets/icons/session/rename.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/crates/nyaterm-app/assets/icons/session/save.svg b/crates/nyaterm-app/assets/icons/session/save.svg index 19015da7..b672e6b2 100644 --- a/crates/nyaterm-app/assets/icons/session/save.svg +++ b/crates/nyaterm-app/assets/icons/session/save.svg @@ -1 +1 @@ - + \ No newline at end of file diff --git a/crates/nyaterm-app/assets/icons/session/stop.svg b/crates/nyaterm-app/assets/icons/session/stop.svg index 5757171a..48d3f2d7 100644 --- a/crates/nyaterm-app/assets/icons/session/stop.svg +++ b/crates/nyaterm-app/assets/icons/session/stop.svg @@ -1 +1 @@ - + \ No newline at end of file diff --git a/crates/nyaterm-app/assets/icons/sessions.svg b/crates/nyaterm-app/assets/icons/sessions.svg index e9dfd234..f39e2a7e 100644 --- a/crates/nyaterm-app/assets/icons/sessions.svg +++ b/crates/nyaterm-app/assets/icons/sessions.svg @@ -1 +1 @@ - + \ No newline at end of file diff --git a/crates/nyaterm-app/assets/icons/settings.svg b/crates/nyaterm-app/assets/icons/settings.svg index 0c704d08..f255a580 100644 --- a/crates/nyaterm-app/assets/icons/settings.svg +++ b/crates/nyaterm-app/assets/icons/settings.svg @@ -1 +1 @@ - + \ No newline at end of file diff --git a/crates/nyaterm-app/assets/icons/sync.svg b/crates/nyaterm-app/assets/icons/sync.svg index 497eb6b0..0b5b51ec 100644 --- a/crates/nyaterm-app/assets/icons/sync.svg +++ b/crates/nyaterm-app/assets/icons/sync.svg @@ -1 +1 @@ - + \ No newline at end of file diff --git a/crates/nyaterm-app/assets/icons/transfer/clear-all.svg b/crates/nyaterm-app/assets/icons/transfer/clear-all.svg index 395a2960..8aab88ac 100644 --- a/crates/nyaterm-app/assets/icons/transfer/clear-all.svg +++ b/crates/nyaterm-app/assets/icons/transfer/clear-all.svg @@ -1 +1 @@ - + \ No newline at end of file diff --git a/crates/nyaterm-app/assets/icons/transfer/pause.svg b/crates/nyaterm-app/assets/icons/transfer/pause.svg index 917d6006..c20e1563 100644 --- a/crates/nyaterm-app/assets/icons/transfer/pause.svg +++ b/crates/nyaterm-app/assets/icons/transfer/pause.svg @@ -1 +1 @@ - + \ No newline at end of file diff --git a/crates/nyaterm-app/assets/icons/transfer/play.svg b/crates/nyaterm-app/assets/icons/transfer/play.svg index 564a5e05..c5e1a4c4 100644 --- a/crates/nyaterm-app/assets/icons/transfer/play.svg +++ b/crates/nyaterm-app/assets/icons/transfer/play.svg @@ -1 +1 @@ - + \ No newline at end of file diff --git a/crates/nyaterm-app/assets/icons/transfer/playlist-remove.svg b/crates/nyaterm-app/assets/icons/transfer/playlist-remove.svg index 8bb85c17..99454437 100644 --- a/crates/nyaterm-app/assets/icons/transfer/playlist-remove.svg +++ b/crates/nyaterm-app/assets/icons/transfer/playlist-remove.svg @@ -1 +1 @@ - + \ No newline at end of file diff --git a/crates/nyaterm-app/assets/icons/transfer/stop.svg b/crates/nyaterm-app/assets/icons/transfer/stop.svg index 8f245fe7..a968282c 100644 --- a/crates/nyaterm-app/assets/icons/transfer/stop.svg +++ b/crates/nyaterm-app/assets/icons/transfer/stop.svg @@ -1 +1 @@ - + \ No newline at end of file diff --git a/crates/nyaterm-app/assets/icons/translation.svg b/crates/nyaterm-app/assets/icons/translation.svg index 9e8557ef..9b806048 100644 --- a/crates/nyaterm-app/assets/icons/translation.svg +++ b/crates/nyaterm-app/assets/icons/translation.svg @@ -1 +1 @@ - + \ No newline at end of file diff --git a/crates/nyaterm-app/assets/icons/view-compact.svg b/crates/nyaterm-app/assets/icons/view-compact.svg index 8d83dc99..4beb2cd9 100644 --- a/crates/nyaterm-app/assets/icons/view-compact.svg +++ b/crates/nyaterm-app/assets/icons/view-compact.svg @@ -1 +1 @@ - + \ No newline at end of file diff --git a/crates/nyaterm-app/assets/icons/view-grid.svg b/crates/nyaterm-app/assets/icons/view-grid.svg index 5917331a..b732680d 100644 --- a/crates/nyaterm-app/assets/icons/view-grid.svg +++ b/crates/nyaterm-app/assets/icons/view-grid.svg @@ -1 +1 @@ - + \ No newline at end of file diff --git a/crates/nyaterm-app/assets/icons/view-list.svg b/crates/nyaterm-app/assets/icons/view-list.svg index 9fa9d2a5..0144a900 100644 --- a/crates/nyaterm-app/assets/icons/view-list.svg +++ b/crates/nyaterm-app/assets/icons/view-list.svg @@ -1 +1 @@ - + \ No newline at end of file diff --git a/crates/nyaterm-app/assets/icons/warning.svg b/crates/nyaterm-app/assets/icons/warning.svg new file mode 100644 index 00000000..05fbc02c --- /dev/null +++ b/crates/nyaterm-app/assets/icons/warning.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/crates/nyaterm-app/assets/icons/window/close.svg b/crates/nyaterm-app/assets/icons/window/close.svg index 9fea7d64..a1c97efd 100644 --- a/crates/nyaterm-app/assets/icons/window/close.svg +++ b/crates/nyaterm-app/assets/icons/window/close.svg @@ -1 +1 @@ - + \ No newline at end of file diff --git a/crates/nyaterm-app/assets/icons/window/maximize.svg b/crates/nyaterm-app/assets/icons/window/maximize.svg index 13209481..bc0f29a9 100644 --- a/crates/nyaterm-app/assets/icons/window/maximize.svg +++ b/crates/nyaterm-app/assets/icons/window/maximize.svg @@ -1 +1 @@ - + \ No newline at end of file diff --git a/crates/nyaterm-app/assets/icons/window/minimize.svg b/crates/nyaterm-app/assets/icons/window/minimize.svg index 7ff8e8f3..ff59c67a 100644 --- a/crates/nyaterm-app/assets/icons/window/minimize.svg +++ b/crates/nyaterm-app/assets/icons/window/minimize.svg @@ -1 +1 @@ - + \ No newline at end of file diff --git a/crates/nyaterm-app/assets/icons/window/restore.svg b/crates/nyaterm-app/assets/icons/window/restore.svg index 8d05edfc..efd9cebb 100644 --- a/crates/nyaterm-app/assets/icons/window/restore.svg +++ b/crates/nyaterm-app/assets/icons/window/restore.svg @@ -1 +1 @@ - + \ No newline at end of file diff --git a/crates/nyaterm-app/assets/icons/workspace.svg b/crates/nyaterm-app/assets/icons/workspace.svg new file mode 100644 index 00000000..16822956 --- /dev/null +++ b/crates/nyaterm-app/assets/icons/workspace.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/crates/nyaterm-app/build.rs b/crates/nyaterm-app/build.rs new file mode 100644 index 00000000..fadc68c4 --- /dev/null +++ b/crates/nyaterm-app/build.rs @@ -0,0 +1,7 @@ +fn main() { + // `rust_embed` expands to `include_bytes!` for the files that exist when the + // macro runs, so cargo only tracks those paths. Without this, adding or + // removing an asset leaves a stale `EmbeddedAssets` and the new icon 404s at + // runtime until something else forces `assets.rs` to recompile. + println!("cargo:rerun-if-changed=assets"); +} diff --git a/crates/nyaterm-app/src/assets.rs b/crates/nyaterm-app/src/assets.rs index 2c7fb12f..506c70f0 100644 --- a/crates/nyaterm-app/src/assets.rs +++ b/crates/nyaterm-app/src/assets.rs @@ -1,279 +1,129 @@ use std::borrow::Cow; use gpui::{AssetSource, Result, SharedString}; +use rust_embed::RustEmbed; + +/// Bundled UI assets, addressed by the same paths the desktop crate passes to +/// `svg()` and `img()`. +/// +/// The directory prefix encodes how an asset is meant to be painted, because the +/// two GPUI elements are not interchangeable: +/// +/// * `icons/**` — monochrome. `svg()` rasterizes to an alpha mask and keeps only +/// the coverage channel, so the file's own paints are discarded and the color +/// comes entirely from `.text_color()`. +/// * `color/**` — full color. `img()` keeps the rasterized pixels, and the asset +/// cannot be tinted. +/// +/// Invariant: nothing under `icons/` may contain a raster `` payload. +/// GPUI builds resvg without the `raster-images` feature, so embedded bitmaps are +/// skipped silently — the icon renders as nothing at all rather than failing. +/// `color/**` carries real `.png` files instead of base64-in-SVG for that reason. +#[derive(RustEmbed)] +#[folder = "assets/"] +#[include = "icons/**/*.svg"] +#[include = "color/**/*.svg"] +#[include = "color/**/*.png"] +pub struct EmbeddedAssets; /// Bundled SVG assets for the native shell (activity icons, logo, connection icons). pub struct NyaTermAssets; -const ICON_FILES: &[&str] = &[ - "icons/logo.svg", - "icons/files.svg", - "icons/network.svg", - "icons/auth.svg", - "icons/sync.svg", - "icons/settings.svg", - "icons/connections.svg", - "icons/ai.svg", - "icons/sessions.svg", - "icons/history.svg", - "icons/resources.svg", - "icons/processes.svg", - "icons/docker.svg", - "icons/commands.svg", - "icons/send.svg", - "icons/record.svg", - "icons/lock.svg", - "icons/chevron-down.svg", - "icons/chevron-up.svg", - "icons/check.svg", - "icons/copy.svg", - "icons/quote.svg", - "icons/eye.svg", - "icons/import.svg", - "icons/import/xshell.svg", - "icons/import/mobaxterm.svg", - "icons/import/windterm.svg", - "icons/pin.svg", - "icons/view-compact.svg", - "icons/view-grid.svg", - "icons/view-list.svg", - "icons/window/minimize.svg", - "icons/window/maximize.svg", - "icons/window/restore.svg", - "icons/window/close.svg", - "icons/session/more.svg", - "icons/session/save.svg", - "icons/session/stop.svg", - "icons/session/record.svg", - "icons/session/disconnect.svg", - "icons/session/reconnect.svg", - "icons/session/rename.svg", - "icons/translation.svg", - "icons/migration.svg", - "icons/conn/server.svg", - "icons/conn/terminal.svg", - "icons/conn/telnet.svg", - "icons/conn/serial.svg", - "icons/conn/linux.svg", - "icons/conn/ubuntu.svg", - "icons/conn/debian.svg", - "icons/conn/apple.svg", - "icons/conn/windows.svg", - "icons/conn/docker.svg", - "icons/conn/python.svg", - "icons/conn/github.svg", - "icons/conn/folder.svg", - "icons/conn/file.svg", - "icons/conn/connect.svg", - "icons/conn/symlink.svg", - "icons/net/move.svg", - "icons/net/delete.svg", - "icons/net/edit.svg", - "icons/conn/sort.svg", - "icons/conn/more.svg", - "icons/conn/flash.svg", - "icons/conn/add.svg", - "icons/fe/new-file.svg", - "icons/fe/new-folder.svg", - "icons/fe/upload.svg", - "icons/fe/upload-folder.svg", - "icons/fe/download.svg", - "icons/fe/delete.svg", - "icons/fe/up.svg", - "icons/fe/refresh.svg", - "icons/fe/search.svg", - "icons/fe/back.svg", - "icons/fe/forward.svg", - "icons/fe/star.svg", - "icons/fe/star-outline.svg", - "icons/fe/sync.svg", - "icons/fe/paste.svg", - "icons/fe/bookmark-remove.svg", - "icons/transfer/clear-all.svg", - "icons/transfer/pause.svg", - "icons/transfer/play.svg", - "icons/transfer/playlist-remove.svg", - "icons/transfer/stop.svg", - "icons/menu/article.svg", - "icons/menu/book.svg", - "icons/menu/broadcast.svg", - "icons/menu/export.svg", - "icons/menu/fit.svg", - "icons/menu/horizontal.svg", - "icons/menu/info.svg", - "icons/menu/menu.svg", - "icons/menu/palette.svg", - "icons/menu/reset.svg", - "icons/menu/sidebar.svg", - "icons/menu/split.svg", - "icons/menu/update.svg", - "icons/menu/vertical.svg", - "icons/menu/zoom-in.svg", - "icons/menu/zoom-out.svg", - "icons/ai/history.svg", - "icons/ai/settings.svg", - "icons/ai/new.svg", - "icons/ai/send.svg", - "icons/ai/stop.svg", - "icons/ai/search.svg", - "icons/ai/exec-confirm.svg", - "icons/ai/exec-smart.svg", - "icons/ai/exec-auto.svg", -]; - -fn icon_bytes(path: &str) -> Option<&'static [u8]> { - match path { - "icons/logo.svg" => Some(include_bytes!("../assets/icons/logo.svg")), - "icons/files.svg" => Some(include_bytes!("../assets/icons/files.svg")), - "icons/network.svg" => Some(include_bytes!("../assets/icons/network.svg")), - "icons/auth.svg" => Some(include_bytes!("../assets/icons/auth.svg")), - "icons/sync.svg" => Some(include_bytes!("../assets/icons/sync.svg")), - "icons/settings.svg" => Some(include_bytes!("../assets/icons/settings.svg")), - "icons/connections.svg" => Some(include_bytes!("../assets/icons/connections.svg")), - "icons/ai.svg" => Some(include_bytes!("../assets/icons/ai.svg")), - "icons/sessions.svg" => Some(include_bytes!("../assets/icons/sessions.svg")), - "icons/history.svg" => Some(include_bytes!("../assets/icons/history.svg")), - "icons/resources.svg" => Some(include_bytes!("../assets/icons/resources.svg")), - "icons/processes.svg" => Some(include_bytes!("../assets/icons/processes.svg")), - "icons/docker.svg" => Some(include_bytes!("../assets/icons/docker.svg")), - "icons/commands.svg" => Some(include_bytes!("../assets/icons/commands.svg")), - "icons/send.svg" => Some(include_bytes!("../assets/icons/send.svg")), - "icons/record.svg" => Some(include_bytes!("../assets/icons/record.svg")), - "icons/lock.svg" => Some(include_bytes!("../assets/icons/lock.svg")), - "icons/chevron-down.svg" => Some(include_bytes!("../assets/icons/chevron-down.svg")), - "icons/chevron-up.svg" => Some(include_bytes!("../assets/icons/chevron-up.svg")), - "icons/check.svg" => Some(include_bytes!("../assets/icons/check.svg")), - "icons/copy.svg" => Some(include_bytes!("../assets/icons/copy.svg")), - "icons/quote.svg" => Some(include_bytes!("../assets/icons/quote.svg")), - "icons/eye.svg" => Some(include_bytes!("../assets/icons/eye.svg")), - "icons/import.svg" => Some(include_bytes!("../assets/icons/import.svg")), - "icons/import/xshell.svg" => Some(include_bytes!("../assets/icons/import/xshell.svg")), - "icons/import/mobaxterm.svg" => { - Some(include_bytes!("../assets/icons/import/mobaxterm.svg")) - } - "icons/import/windterm.svg" => Some(include_bytes!("../assets/icons/import/windterm.svg")), - "icons/pin.svg" => Some(include_bytes!("../assets/icons/pin.svg")), - "icons/view-compact.svg" => Some(include_bytes!("../assets/icons/view-compact.svg")), - "icons/view-grid.svg" => Some(include_bytes!("../assets/icons/view-grid.svg")), - "icons/view-list.svg" => Some(include_bytes!("../assets/icons/view-list.svg")), - "icons/window/minimize.svg" => Some(include_bytes!("../assets/icons/window/minimize.svg")), - "icons/window/maximize.svg" => Some(include_bytes!("../assets/icons/window/maximize.svg")), - "icons/window/restore.svg" => Some(include_bytes!("../assets/icons/window/restore.svg")), - "icons/window/close.svg" => Some(include_bytes!("../assets/icons/window/close.svg")), - "icons/session/more.svg" => Some(include_bytes!("../assets/icons/session/more.svg")), - "icons/session/save.svg" => Some(include_bytes!("../assets/icons/session/save.svg")), - "icons/session/stop.svg" => Some(include_bytes!("../assets/icons/session/stop.svg")), - "icons/session/record.svg" => Some(include_bytes!("../assets/icons/session/record.svg")), - "icons/session/disconnect.svg" => { - Some(include_bytes!("../assets/icons/session/disconnect.svg")) - } - "icons/session/reconnect.svg" => { - Some(include_bytes!("../assets/icons/session/reconnect.svg")) - } - "icons/session/rename.svg" => Some(include_bytes!("../assets/icons/session/rename.svg")), - "icons/translation.svg" => Some(include_bytes!("../assets/icons/translation.svg")), - "icons/migration.svg" => Some(include_bytes!("../assets/icons/migration.svg")), - "icons/conn/server.svg" => Some(include_bytes!("../assets/icons/conn/server.svg")), - "icons/conn/terminal.svg" => Some(include_bytes!("../assets/icons/conn/terminal.svg")), - "icons/conn/telnet.svg" => Some(include_bytes!("../assets/icons/conn/telnet.svg")), - "icons/conn/serial.svg" => Some(include_bytes!("../assets/icons/conn/serial.svg")), - "icons/conn/linux.svg" => Some(include_bytes!("../assets/icons/conn/linux.svg")), - "icons/conn/ubuntu.svg" => Some(include_bytes!("../assets/icons/conn/ubuntu.svg")), - "icons/conn/debian.svg" => Some(include_bytes!("../assets/icons/conn/debian.svg")), - "icons/conn/apple.svg" => Some(include_bytes!("../assets/icons/conn/apple.svg")), - "icons/conn/windows.svg" => Some(include_bytes!("../assets/icons/conn/windows.svg")), - "icons/conn/docker.svg" => Some(include_bytes!("../assets/icons/conn/docker.svg")), - "icons/conn/python.svg" => Some(include_bytes!("../assets/icons/conn/python.svg")), - "icons/conn/github.svg" => Some(include_bytes!("../assets/icons/conn/github.svg")), - "icons/conn/folder.svg" => Some(include_bytes!("../assets/icons/conn/folder.svg")), - "icons/conn/file.svg" => Some(include_bytes!("../assets/icons/conn/file.svg")), - "icons/conn/connect.svg" => Some(include_bytes!("../assets/icons/conn/connect.svg")), - "icons/conn/symlink.svg" => Some(include_bytes!("../assets/icons/conn/symlink.svg")), - "icons/net/move.svg" => Some(include_bytes!("../assets/icons/net/move.svg")), - "icons/net/delete.svg" => Some(include_bytes!("../assets/icons/net/delete.svg")), - "icons/net/edit.svg" => Some(include_bytes!("../assets/icons/net/edit.svg")), - "icons/conn/sort.svg" => Some(include_bytes!("../assets/icons/conn/sort.svg")), - "icons/conn/more.svg" => Some(include_bytes!("../assets/icons/conn/more.svg")), - "icons/conn/flash.svg" => Some(include_bytes!("../assets/icons/conn/flash.svg")), - "icons/conn/add.svg" => Some(include_bytes!("../assets/icons/conn/add.svg")), - "icons/fe/new-file.svg" => Some(include_bytes!("../assets/icons/fe/new-file.svg")), - "icons/fe/new-folder.svg" => Some(include_bytes!("../assets/icons/fe/new-folder.svg")), - "icons/fe/upload.svg" => Some(include_bytes!("../assets/icons/fe/upload.svg")), - "icons/fe/upload-folder.svg" => { - Some(include_bytes!("../assets/icons/fe/upload-folder.svg")) - } - "icons/fe/download.svg" => Some(include_bytes!("../assets/icons/fe/download.svg")), - "icons/fe/delete.svg" => Some(include_bytes!("../assets/icons/fe/delete.svg")), - "icons/fe/up.svg" => Some(include_bytes!("../assets/icons/fe/up.svg")), - "icons/fe/refresh.svg" => Some(include_bytes!("../assets/icons/fe/refresh.svg")), - "icons/fe/search.svg" => Some(include_bytes!("../assets/icons/fe/search.svg")), - "icons/fe/back.svg" => Some(include_bytes!("../assets/icons/fe/back.svg")), - "icons/fe/forward.svg" => Some(include_bytes!("../assets/icons/fe/forward.svg")), - "icons/fe/star.svg" => Some(include_bytes!("../assets/icons/fe/star.svg")), - "icons/fe/star-outline.svg" => Some(include_bytes!("../assets/icons/fe/star-outline.svg")), - "icons/fe/sync.svg" => Some(include_bytes!("../assets/icons/fe/sync.svg")), - "icons/fe/paste.svg" => Some(include_bytes!("../assets/icons/fe/paste.svg")), - "icons/fe/bookmark-remove.svg" => { - Some(include_bytes!("../assets/icons/fe/bookmark-remove.svg")) - } - "icons/transfer/clear-all.svg" => { - Some(include_bytes!("../assets/icons/transfer/clear-all.svg")) - } - "icons/transfer/pause.svg" => Some(include_bytes!("../assets/icons/transfer/pause.svg")), - "icons/transfer/play.svg" => Some(include_bytes!("../assets/icons/transfer/play.svg")), - "icons/transfer/playlist-remove.svg" => Some(include_bytes!( - "../assets/icons/transfer/playlist-remove.svg" - )), - "icons/transfer/stop.svg" => Some(include_bytes!("../assets/icons/transfer/stop.svg")), - "icons/menu/article.svg" => Some(include_bytes!("../assets/icons/menu/article.svg")), - "icons/menu/book.svg" => Some(include_bytes!("../assets/icons/menu/book.svg")), - "icons/menu/broadcast.svg" => Some(include_bytes!("../assets/icons/menu/broadcast.svg")), - "icons/menu/export.svg" => Some(include_bytes!("../assets/icons/menu/export.svg")), - "icons/menu/fit.svg" => Some(include_bytes!("../assets/icons/menu/fit.svg")), - "icons/menu/horizontal.svg" => Some(include_bytes!("../assets/icons/menu/horizontal.svg")), - "icons/menu/info.svg" => Some(include_bytes!("../assets/icons/menu/info.svg")), - "icons/menu/menu.svg" => Some(include_bytes!("../assets/icons/menu/menu.svg")), - "icons/menu/palette.svg" => Some(include_bytes!("../assets/icons/menu/palette.svg")), - "icons/menu/reset.svg" => Some(include_bytes!("../assets/icons/menu/reset.svg")), - "icons/menu/sidebar.svg" => Some(include_bytes!("../assets/icons/menu/sidebar.svg")), - "icons/menu/split.svg" => Some(include_bytes!("../assets/icons/menu/split.svg")), - "icons/menu/update.svg" => Some(include_bytes!("../assets/icons/menu/update.svg")), - "icons/menu/vertical.svg" => Some(include_bytes!("../assets/icons/menu/vertical.svg")), - "icons/menu/zoom-in.svg" => Some(include_bytes!("../assets/icons/menu/zoom-in.svg")), - "icons/menu/zoom-out.svg" => Some(include_bytes!("../assets/icons/menu/zoom-out.svg")), - "icons/ai/history.svg" => Some(include_bytes!("../assets/icons/ai/history.svg")), - "icons/ai/settings.svg" => Some(include_bytes!("../assets/icons/ai/settings.svg")), - "icons/ai/new.svg" => Some(include_bytes!("../assets/icons/ai/new.svg")), - "icons/ai/send.svg" => Some(include_bytes!("../assets/icons/ai/send.svg")), - "icons/ai/stop.svg" => Some(include_bytes!("../assets/icons/ai/stop.svg")), - "icons/ai/search.svg" => Some(include_bytes!("../assets/icons/ai/search.svg")), - "icons/ai/exec-confirm.svg" => Some(include_bytes!("../assets/icons/ai/exec-confirm.svg")), - "icons/ai/exec-smart.svg" => Some(include_bytes!("../assets/icons/ai/exec-smart.svg")), - "icons/ai/exec-auto.svg" => Some(include_bytes!("../assets/icons/ai/exec-auto.svg")), - _ => None, - } -} - impl AssetSource for NyaTermAssets { fn load(&self, path: &str) -> Result>> { - let normalized = path.trim_start_matches('/'); - Ok(icon_bytes(normalized).map(|bytes| Cow::Borrowed(bytes))) + Ok(EmbeddedAssets::get(path.trim_start_matches('/')).map(|file| file.data)) } fn list(&self, path: &str) -> Result> { - let normalized = path.trim_start_matches('/'); - if normalized.is_empty() - || normalized == "icons" - || normalized == "icons/conn" - || normalized == "icons/fe" - || normalized == "icons/ai" - || normalized == "icons/menu" - || normalized == "icons/transfer" - || normalized == "icons/window" + let prefix = path.trim_start_matches('/').trim_end_matches('/'); + Ok(EmbeddedAssets::iter() + .filter(|entry| { + prefix.is_empty() + || entry + .strip_prefix(prefix) + .is_some_and(|rest| rest.starts_with('/')) + }) + .map(|entry| SharedString::from(entry.into_owned())) + .collect()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + /// `img()` rasterizes at `SMOOTH_SVG_SCALE_FACTOR` (2.0) times the asset's + /// *intrinsic* size, not its display size, and `App::fetch_asset` never evicts + /// the result. A logo left at its authoring size of 256px would hold a 512x512 + /// BGRA buffer (1 MB) to paint a 16px row icon. + const MAX_COLOR_INTRINSIC_PX: f32 = 64.0; + + fn parse(path: &str, bytes: &[u8]) -> usvg::Tree { + usvg::Tree::from_data(bytes, &usvg::Options::default()) + .unwrap_or_else(|err| panic!("{path} is not parseable by usvg: {err}")) + } + + #[test] + fn every_embedded_svg_parses_with_a_nonzero_size() { + for path in EmbeddedAssets::iter().filter(|path| path.ends_with(".svg")) { + let file = EmbeddedAssets::get(&path).expect("iter() yielded a missing asset"); + let size = parse(&path, &file.data).size(); + assert!( + size.width() > 0.0 && size.height() > 0.0, + "{path} resolves to a zero size, so it would paint nothing" + ); + } + } + + #[test] + fn no_icon_asset_carries_a_raster_payload() { + fn find_raster(group: &usvg::Group, path: &str) { + for node in group.children() { + match node { + usvg::Node::Image(image) => assert!( + matches!(image.kind(), usvg::ImageKind::SVG(_)), + "{path} embeds a raster image. GPUI builds resvg without the \ + `raster-images` feature, so this renders blank. Extract the \ + payload to a real file under color/ and paint it with img()." + ), + usvg::Node::Group(child) => find_raster(child, path), + _ => {} + } + } + } + + for path in EmbeddedAssets::iter().filter(|path| path.starts_with("icons/")) { + let file = EmbeddedAssets::get(&path).expect("iter() yielded a missing asset"); + find_raster(parse(&path, &file.data).root(), &path); + } + } + + #[test] + fn color_assets_stay_within_the_raster_budget() { + for path in EmbeddedAssets::iter() + .filter(|path| path.starts_with("color/") && path.ends_with(".svg")) { - Ok(ICON_FILES.iter().map(|item| (*item).into()).collect()) - } else { - Ok(Vec::new()) + let file = EmbeddedAssets::get(&path).expect("iter() yielded a missing asset"); + let size = parse(&path, &file.data).size(); + let largest = size.width().max(size.height()); + assert!( + largest <= MAX_COLOR_INTRINSIC_PX, + "{path} has an intrinsic size of {largest}px; img() would hold a \ + {}x{} raster forever. Set width/height to {MAX_COLOR_INTRINSIC_PX}px \ + or less (leave viewBox alone).", + (largest * 2.0) as u32, + (largest * 2.0) as u32, + ); } + } + + #[test] + fn list_returns_only_entries_below_the_requested_prefix() { + let all = NyaTermAssets.list("").expect("list all"); + let icons = NyaTermAssets.list("icons").expect("list icons"); + + assert!(!icons.is_empty(), "no icons are embedded"); + assert!(icons.len() <= all.len()); + assert!(icons.iter().all(|entry| entry.starts_with("icons/"))); } } diff --git a/crates/nyaterm-app/src/lib.rs b/crates/nyaterm-app/src/lib.rs new file mode 100644 index 00000000..50cfe440 --- /dev/null +++ b/crates/nyaterm-app/src/lib.rs @@ -0,0 +1,7 @@ +//! Bundled assets for the NyaTerm shell. +//! +//! The binary is the real entry point; this library exists so the asset registry +//! is reachable from tests and from the `icon_gallery` example, which is how a +//! newly added icon gets proven to actually render. + +pub mod assets; diff --git a/crates/nyaterm-app/src/main.rs b/crates/nyaterm-app/src/main.rs index 1db62635..3c354aee 100644 --- a/crates/nyaterm-app/src/main.rs +++ b/crates/nyaterm-app/src/main.rs @@ -1,7 +1,6 @@ -mod assets; - use anyhow::Context as _; use gpui::{App, AppContext, Application, Bounds, WindowBounds, WindowOptions, px, size}; +use nyaterm_app::assets; use nyaterm_core::{AppRuntime, LOG_FILE_PREFIX, LOG_FILE_SUFFIX}; use nyaterm_desktop::AppShell; use tracing_appender::non_blocking::WorkerGuard; diff --git a/crates/nyaterm-core/src/cloud_sync.rs b/crates/nyaterm-core/src/cloud_sync.rs index b0fc5962..3c8e096a 100644 --- a/crates/nyaterm-core/src/cloud_sync.rs +++ b/crates/nyaterm-core/src/cloud_sync.rs @@ -2766,6 +2766,7 @@ mod tests { description: None, sort_order: 0, icon: None, + icon_auto_detect: None, auth: None, network: None, post_login: None, diff --git a/crates/nyaterm-core/src/models.rs b/crates/nyaterm-core/src/models.rs index 274f8e9e..594a7f3d 100644 --- a/crates/nyaterm-core/src/models.rs +++ b/crates/nyaterm-core/src/models.rs @@ -426,6 +426,14 @@ pub struct SavedConnection { pub sort_order: i32, #[serde(default)] pub icon: Option, + /// Whether `icon` may be replaced by one detected from the remote system. + /// + /// `None` means "not configured", which reads as enabled only while no icon + /// has been chosen — see [`SavedConnection::icon_auto_detect_enabled`]. Kept + /// as an `Option` and skipped when empty so files round-trip unchanged + /// through builds that predate the field. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub icon_auto_detect: Option, #[serde(default)] pub auth: Option, #[serde(default)] @@ -441,6 +449,16 @@ pub struct SavedConnection { } impl SavedConnection { + /// Whether the icon may be replaced by one inferred from the remote system. + /// + /// An unset flag defaults to "yes, until the user picks something", which is + /// what keeps auto-detection from ever overwriting a deliberate choice made + /// before this field existed. + pub fn icon_auto_detect_enabled(&self) -> bool { + self.icon_auto_detect + .unwrap_or_else(|| self.icon.as_deref().is_none_or(str::is_empty)) + } + pub fn kind_label(&self) -> &'static str { match self.config { ConnectionType::Ssh { .. } => "SSH", @@ -1452,6 +1470,7 @@ mod tests { description: None, sort_order: 0, icon: None, + icon_auto_detect: None, auth: None, network: None, post_login: None, @@ -1462,4 +1481,48 @@ mod tests { assert_eq!(connection.endpoint(), "zsh in /data"); } + + #[test] + fn icon_auto_detect_defaults_to_filling_in_a_blank_only() { + let mut connection: SavedConnection = serde_json::from_str( + r#"{"id":"c1","name":"Box","type":"ssh","host":"h","port":22,"username":"root"}"#, + ) + .expect("valid connection"); + + // Unset flag, no icon: detection may fill one in. + assert!(connection.icon_auto_detect_enabled()); + + // Unset flag but an icon chosen by an older build: leave it alone. + connection.icon = Some("ubuntu".to_string()); + assert!(!connection.icon_auto_detect_enabled()); + + // An explicit flag always wins over the heuristic, either way. + connection.icon_auto_detect = Some(true); + assert!(connection.icon_auto_detect_enabled()); + connection.icon = None; + connection.icon_auto_detect = Some(false); + assert!(!connection.icon_auto_detect_enabled()); + } + + #[test] + fn icon_auto_detect_round_trips_and_stays_absent_when_unset() { + let connection: SavedConnection = serde_json::from_str( + r#"{"id":"c1","name":"Box","type":"ssh","host":"h","port":22,"username":"root"}"#, + ) + .expect("valid connection"); + + // Files written by builds predating the field must round-trip byte-for- + // byte, so an unset flag is never serialized. + let json = serde_json::to_string(&connection).expect("serializes"); + assert!(!json.contains("icon_auto_detect"), "{json}"); + + let explicit = SavedConnection { + icon_auto_detect: Some(false), + ..connection + }; + let reloaded: SavedConnection = + serde_json::from_str(&serde_json::to_string(&explicit).expect("serializes")) + .expect("reloads"); + assert_eq!(reloaded.icon_auto_detect, Some(false)); + } } diff --git a/crates/nyaterm-core/src/session_import.rs b/crates/nyaterm-core/src/session_import.rs index dfad8897..a397030b 100644 --- a/crates/nyaterm-core/src/session_import.rs +++ b/crates/nyaterm-core/src/session_import.rs @@ -1150,6 +1150,7 @@ fn import_legacy_sessions( description: None, sort_order: 0, icon: None, + icon_auto_detect: None, auth: Some(ConnectionAuth { mode: sess.auth_type, password_id: None, @@ -1208,6 +1209,9 @@ fn import_prepared_nyaterm_json( description: conn.description, sort_order: conn.sort_order, icon: conn.icon, + // Imported sessions carry whatever icon the source client had; leave + // auto-detection unconfigured so it only fills in a blank. + icon_auto_detect: None, auth: conn.auth, network: None, post_login: None, diff --git a/crates/nyaterm-core/src/storage.rs b/crates/nyaterm-core/src/storage.rs index 31828395..fa9d06a0 100644 --- a/crates/nyaterm-core/src/storage.rs +++ b/crates/nyaterm-core/src/storage.rs @@ -1506,6 +1506,7 @@ mod tests { description: Some("Primary".to_string()), sort_order: 0, icon: None, + icon_auto_detect: None, auth: Some(ConnectionAuth { mode: "password".to_string(), password: Some("secret".to_string()), @@ -1570,6 +1571,7 @@ mod tests { description: None, sort_order: 0, icon: None, + icon_auto_detect: None, auth: None, network: None, post_login: None, @@ -1645,6 +1647,7 @@ mod tests { description: Some("Primary".to_string()), sort_order: 0, icon: None, + icon_auto_detect: None, auth: Some(ConnectionAuth { mode: "password".to_string(), password: Some("session-secret".to_string()), @@ -1811,6 +1814,7 @@ mod tests { description: None, sort_order: 0, icon: None, + icon_auto_detect: None, auth: None, network: None, post_login: None, @@ -1867,6 +1871,7 @@ mod tests { description: None, sort_order: 0, icon: None, + icon_auto_detect: None, auth: None, network: None, post_login: None, @@ -1937,6 +1942,7 @@ mod tests { description: None, sort_order: 0, icon: None, + icon_auto_detect: None, auth: None, network: None, post_login: None, @@ -2004,6 +2010,7 @@ mod tests { description: None, sort_order: 0, icon: None, + icon_auto_detect: None, auth: None, network: None, post_login: None, @@ -2045,6 +2052,7 @@ mod tests { description: None, sort_order: 0, icon: None, + icon_auto_detect: None, auth: None, network: None, post_login: None, @@ -2112,6 +2120,7 @@ mod tests { description: None, sort_order: 0, icon: None, + icon_auto_detect: None, auth: None, network: None, post_login: None, @@ -2148,6 +2157,7 @@ mod tests { description: None, sort_order: 0, icon: None, + icon_auto_detect: None, auth: Some(ConnectionAuth { mode: "password".to_string(), ..Default::default() diff --git a/crates/nyaterm-desktop/src/features/commands/mod.rs b/crates/nyaterm-desktop/src/features/commands/mod.rs index cebaabef..ebd09a86 100644 --- a/crates/nyaterm-desktop/src/features/commands/mod.rs +++ b/crates/nyaterm-desktop/src/features/commands/mod.rs @@ -7,7 +7,7 @@ mod quick_command_runtime; mod state; pub(in crate::features) use quick_command_runtime::{ - QUICK_COMMAND_COLOR_OPTIONS, QUICK_COMMAND_ICON_OPTIONS, quick_command_category_label, + QUICK_COMMAND_COLOR_OPTIONS, quick_command_category_label, quick_command_sort_mode_from_setting, quick_command_view_mode_from_setting, }; pub(in crate::features) use state::{QuickCommandFeatureFocus, QuickCommandFeatureState}; diff --git a/crates/nyaterm-desktop/src/features/commands/quick_command_runtime/mod.rs b/crates/nyaterm-desktop/src/features/commands/quick_command_runtime/mod.rs index dcb15c0c..67dc0982 100644 --- a/crates/nyaterm-desktop/src/features/commands/quick_command_runtime/mod.rs +++ b/crates/nyaterm-desktop/src/features/commands/quick_command_runtime/mod.rs @@ -12,39 +12,6 @@ pub(in crate::features) const QUICK_COMMAND_COLOR_OPTIONS: [Option<&str>; 6] = [ Some("yellow"), Some("purple"), ]; -pub(in crate::features) const QUICK_COMMAND_ICON_OPTIONS: [Option<&str>; 31] = [ - None, - Some("terminal"), - Some("code"), - Some("server"), - Some("folder"), - Some("sparkles"), - Some("bolt"), - Some("docker"), - Some("k8s"), - Some("linux"), - Some("ubuntu"), - Some("debian"), - Some("centos"), - Some("fedora"), - Some("apple"), - Some("github"), - Some("gitlab"), - Some("nginx"), - Some("redis"), - Some("postgres"), - Some("mysql"), - Some("mongodb"), - Some("python"), - Some("js"), - Some("ts"), - Some("rust"), - Some("go"), - Some("node"), - Some("php"), - Some("aws"), - Some("gcp"), -]; mod helpers; pub(in crate::features) use helpers::*; diff --git a/crates/nyaterm-desktop/src/features/connections/connection_runtime/auto_icon.rs b/crates/nyaterm-desktop/src/features/connections/connection_runtime/auto_icon.rs new file mode 100644 index 00000000..b8ef5ba3 --- /dev/null +++ b/crates/nyaterm-desktop/src/features/connections/connection_runtime/auto_icon.rs @@ -0,0 +1,73 @@ +//! Fill in a saved connection's icon from what the remote host reports. +//! +//! Runs off the remote-stats snapshot the resource panel already collects, so no +//! extra round trip is made. Only ever fills in a blank or refreshes a previous +//! auto-detection — a hand-picked icon clears the flag and is never touched. + +use nyaterm_core::ConnectionType; +use nyaterm_transport::SystemInfo; + +use crate::features::{NyaTermApp, infer_connection_icon_key_from_remote_system}; + +impl NyaTermApp { + pub(in crate::features) fn apply_auto_detected_connection_icon( + &mut self, + session_id: &str, + system: &SystemInfo, + ) { + let Some(connection_id) = self + .session_metadata + .get(session_id) + .and_then(|metadata| metadata.source_connection_id.as_deref()) + .map(str::trim) + .filter(|id| !id.is_empty()) + .map(ToOwned::to_owned) + else { + return; + }; + + let Some(connection) = self + .connections + .iter() + .find(|connection| connection.id == connection_id) + else { + return; + }; + + // Only SSH sessions report a remote system worth reading. + if !matches!(connection.config, ConnectionType::Ssh { .. }) + || !connection.icon_auto_detect_enabled() + { + return; + } + + let Some(icon_key) = infer_connection_icon_key_from_remote_system(&system.os, &system.arch) + else { + return; + }; + if connection.icon.as_deref() == Some(icon_key) { + return; + } + + let mut updated = connection.clone(); + updated.icon = Some(icon_key.to_string()); + updated.icon_auto_detect = Some(true); + + match self.persist_saved_connection_with_group(updated, None) { + Ok(connection) => { + self.terminal.view.status = + format!("detected {icon_key} icon for {}", connection.name); + } + Err(error) => { + // A failed icon refresh must not disturb the session, so this is + // reported as a log line rather than surfaced to the user. + tracing::warn!( + target: "nyaterm::connections", + connection_id = %connection_id, + %error, + "failed to persist auto-detected connection icon" + ); + } + } + } +} diff --git a/crates/nyaterm-desktop/src/features/connections/connection_runtime/editor.rs b/crates/nyaterm-desktop/src/features/connections/connection_runtime/editor.rs index 08ba00b4..1ff1bdea 100644 --- a/crates/nyaterm-desktop/src/features/connections/connection_runtime/editor.rs +++ b/crates/nyaterm-desktop/src/features/connections/connection_runtime/editor.rs @@ -47,6 +47,9 @@ impl NyaTermApp { name: String::new(), description: String::new(), icon: None, + // A new connection has no icon yet, so let the first successful + // SSH session fill one in. + icon_auto_detect: true, group_id: parent_group_id.filter(|value| !value.trim().is_empty()), new_group_name: String::new(), pending_group_name: None, @@ -127,6 +130,16 @@ impl NyaTermApp { cx.notify(); } + pub(in crate::features) fn set_connection_editor_icon_auto_detect( + &mut self, + enabled: bool, + cx: &mut Context, + ) { + if self.connection_state.editor.set_icon_auto_detect(enabled) { + cx.notify(); + } + } + pub(in crate::features) fn toggle_connection_editor_menu( &mut self, menu: ConnectionEditorMenu, diff --git a/crates/nyaterm-desktop/src/features/connections/connection_runtime/helpers.rs b/crates/nyaterm-desktop/src/features/connections/connection_runtime/helpers.rs index aacf0632..ff380b9e 100644 --- a/crates/nyaterm-desktop/src/features/connections/connection_runtime/helpers.rs +++ b/crates/nyaterm-desktop/src/features/connections/connection_runtime/helpers.rs @@ -34,12 +34,14 @@ pub(super) fn connection_editor_from_saved( } else { ConnectionEditorPasswordSource::Ask }; + let icon_auto_detect = connection.icon_auto_detect_enabled(); let mut editor = ConnectionEditorState { id: Some(connection.id), kind: ConnectionKindTab::from_connection_type(&connection.config), name: connection.name, description: connection.description.unwrap_or_default(), icon: connection.icon, + icon_auto_detect, group_id: connection.group_id, new_group_name: String::new(), pending_group_name: None, @@ -369,6 +371,11 @@ pub(super) fn build_saved_connection_from_editor( description: non_empty_optional(&editor.description), sort_order: 0, icon: editor.icon.clone().filter(|value| !value.trim().is_empty()), + // Only SSH sessions report a remote system, so nothing else can be + // auto-detected; persist the flag explicitly rather than relying on the + // "unset means enabled while blank" default, which would flip once the + // user chose an icon. + icon_auto_detect: Some(editor.kind == ConnectionKindTab::Ssh && editor.icon_auto_detect), auth, network, post_login, @@ -421,6 +428,7 @@ mod tests { description: None, sort_order: 0, icon: Some("linux".to_string()), + icon_auto_detect: None, auth: None, network: None, post_login: None, @@ -452,6 +460,7 @@ mod tests { description: None, sort_order: 0, icon: None, + icon_auto_detect: None, auth: Some(ConnectionAuth { mode: "password".to_string(), password_id: Some("password-1".to_string()), @@ -500,6 +509,7 @@ mod tests { description: None, sort_order: 0, icon: None, + icon_auto_detect: None, auth: None, network: None, post_login: None, diff --git a/crates/nyaterm-desktop/src/features/connections/connection_runtime/mod.rs b/crates/nyaterm-desktop/src/features/connections/connection_runtime/mod.rs index 5019a384..504b450c 100644 --- a/crates/nyaterm-desktop/src/features/connections/connection_runtime/mod.rs +++ b/crates/nyaterm-desktop/src/features/connections/connection_runtime/mod.rs @@ -2,5 +2,6 @@ mod helpers; pub(in crate::features) use helpers::ConnectionEditorToggle; mod actions; +mod auto_icon; mod editor; mod groups; diff --git a/crates/nyaterm-desktop/src/features/connections/connections/mod.rs b/crates/nyaterm-desktop/src/features/connections/connections/mod.rs index c500d568..5cc08dc0 100644 --- a/crates/nyaterm-desktop/src/features/connections/connections/mod.rs +++ b/crates/nyaterm-desktop/src/features/connections/connections/mod.rs @@ -45,9 +45,9 @@ impl ConnectionDragPreview { impl Render for ConnectionDragPreview { fn render(&mut self, _: &mut Window, _: &mut Context) -> impl IntoElement { - let (kind, accent) = match self.payload.kind { - ConnectionDragKind::Connection => ("⌂", rgb(0x3fb950)), - ConnectionDragKind::Group => ("▸", rgb(0x58a6ff)), + let (icon, accent) = match self.payload.kind { + ConnectionDragKind::Connection => ("icons/brand/server.svg", rgb(0x3fb950)), + ConnectionDragKind::Group => ("icons/conn/folder.svg", rgb(0x58a6ff)), }; div() .pl(self.position.x - px(90.)) @@ -65,7 +65,7 @@ impl Render for ConnectionDragPreview { .border_color(rgb(0x388bfd)) .bg(rgba(0x0d1117ee)) .shadow_lg() - .child(div().text_size(px(13.)).text_color(accent).child(kind)) + .child(crate::features::mono_icon(icon, accent.into(), 13.)) .child( div() .min_w_0() diff --git a/crates/nyaterm-desktop/src/features/connections/state/editor_logic.rs b/crates/nyaterm-desktop/src/features/connections/state/editor_logic.rs index ef128e44..24c40bd3 100644 --- a/crates/nyaterm-desktop/src/features/connections/state/editor_logic.rs +++ b/crates/nyaterm-desktop/src/features/connections/state/editor_logic.rs @@ -101,10 +101,27 @@ pub(super) fn set_connection_editor_icon( .map(str::trim) .filter(|icon| !icon.is_empty()) .map(ToOwned::to_owned); + // Choosing an icon by hand is an explicit decision, so stop letting the + // remote system overwrite it. Clearing the icon hands control back. + editor.icon_auto_detect = editor.icon.is_none(); editor.error = None; true } +pub(super) fn set_connection_editor_icon_auto_detect( + draft: &mut Option, + enabled: bool, +) -> bool { + let Some(editor) = draft.as_mut() else { + return false; + }; + if editor.icon_auto_detect == enabled { + return false; + } + editor.icon_auto_detect = enabled; + true +} + pub(super) fn set_connection_editor_menu_value( draft: &mut Option, menu: ConnectionEditorMenu, diff --git a/crates/nyaterm-desktop/src/features/formatting/connection_icons.rs b/crates/nyaterm-desktop/src/features/formatting/connection_icons.rs deleted file mode 100644 index 4a6a3615..00000000 --- a/crates/nyaterm-desktop/src/features/formatting/connection_icons.rs +++ /dev/null @@ -1,193 +0,0 @@ -/// Tauri `resolveConnectionIcon`: map stored icon key / connection kind to SVG path + color. -#[derive(Debug, Clone, Copy)] -pub(in crate::features) struct ConnectionIconDef { - pub path: &'static str, - pub color: u32, -} - -/// Keys exposed by the Tauri connection icon picker, in its display order. -pub(in crate::features) const CONNECTION_ICON_OPTIONS: &[&str] = &[ - "server", - "server-emerald", - "server-amber", - "server-rose", - "server-violet", - "server-cyan", - "server-slate", - "windows", - "apple", - "android", - "linux", - "ubuntu", - "debian", - "centos", - "fedora", - "arch", - "manjaro", - "opensuse", - "rocky", - "alma", - "alpine", - "kali", - "mint", - "nixos", - "gentoo", - "freebsd", - "raspberrypi", -]; - -pub(in crate::features) fn resolve_connection_icon( - icon_key: Option<&str>, - kind: &str, -) -> ConnectionIconDef { - if let Some(key) = icon_key.map(str::trim).filter(|value| !value.is_empty()) { - if let Some(def) = connection_icon_by_key(key) { - return def; - } - } - default_connection_icon_for_kind(kind) -} - -fn connection_icon_by_key(key: &str) -> Option { - let key = key.to_ascii_lowercase(); - // Server palette (Tauri SERVER_ICONS colors). - let server = match key.as_str() { - "server" => Some(0x60a5fa), - "server-emerald" => Some(0x34d399), - "server-amber" => Some(0xfbbf24), - "server-rose" => Some(0xfb7185), - "server-violet" => Some(0xa78bfa), - "server-cyan" => Some(0x22d3ee), - "server-slate" => Some(0x94a3b8), - _ => None, - }; - if let Some(color) = server { - return Some(ConnectionIconDef { - path: "icons/conn/server.svg", - color, - }); - } - Some(match key.as_str() { - "linux" => ConnectionIconDef { - path: "icons/conn/linux.svg", - color: 0xfcc624, - }, - "ubuntu" => ConnectionIconDef { - path: "icons/conn/ubuntu.svg", - color: 0xe95420, - }, - "debian" => ConnectionIconDef { - path: "icons/conn/debian.svg", - color: 0xa81d33, - }, - "centos" | "fedora" | "arch" | "manjaro" | "opensuse" | "rocky" | "alma" | "alpine" - | "kali" | "mint" | "nixos" | "gentoo" | "freebsd" | "raspberrypi" => ConnectionIconDef { - path: "icons/conn/linux.svg", - color: match key.as_str() { - "centos" => 0xa14f8c, - "fedora" => 0x3c4fb1, - "arch" => 0x1793d1, - "manjaro" => 0x35bf5c, - "opensuse" => 0x73ba25, - "rocky" => 0x10b981, - "alma" => 0xff4649, - "alpine" => 0x0d597f, - "kali" => 0x268bee, - "mint" => 0x87cf3e, - "nixos" => 0x5277c3, - "gentoo" => 0x54487a, - "freebsd" => 0xab2b28, - "raspberrypi" => 0xa22846, - _ => 0xfcc624, - }, - }, - "apple" => ConnectionIconDef { - path: "icons/conn/apple.svg", - color: 0xa2aaad, - }, - "windows" => ConnectionIconDef { - path: "icons/conn/windows.svg", - color: 0x0078d4, - }, - "android" => ConnectionIconDef { - path: "icons/conn/linux.svg", - color: 0x3ddc84, - }, - "docker" => ConnectionIconDef { - path: "icons/conn/docker.svg", - color: 0x2496ed, - }, - "python" => ConnectionIconDef { - path: "icons/conn/python.svg", - color: 0x3776ab, - }, - "github" => ConnectionIconDef { - path: "icons/conn/github.svg", - color: 0xc9d1d9, - }, - "k8s" | "kubernetes" => ConnectionIconDef { - path: "icons/conn/docker.svg", - color: 0x326ce5, - }, - "local" | "terminal" => ConnectionIconDef { - path: "icons/conn/terminal.svg", - color: 0x4ade80, - }, - "telnet" => ConnectionIconDef { - path: "icons/conn/telnet.svg", - color: 0xd29922, - }, - "serial" => ConnectionIconDef { - path: "icons/conn/serial.svg", - color: 0xbc8cff, - }, - "folder" | "group" => ConnectionIconDef { - path: "icons/conn/folder.svg", - color: 0xfbbf24, - }, - // Other QUICK_ICONS keys fall back to colored server glyph. - "nginx" | "redis" | "postgres" | "mysql" | "mongodb" | "js" | "ts" | "rust" | "go" - | "node" | "php" | "aws" | "gcp" | "gitlab" => ConnectionIconDef { - path: "icons/conn/server.svg", - color: match key.as_str() { - "nginx" => 0x009639, - "redis" => 0xdc382d, - "postgres" => 0x4169e1, - "mysql" => 0x4479a1, - "mongodb" => 0x47a248, - "js" => 0xf7df1e, - "ts" => 0x3178c6, - "rust" => 0xdea584, - "go" => 0x00add8, - "node" => 0x339933, - "php" => 0x777bb4, - "aws" => 0xff9900, - "gcp" => 0x4285f4, - "gitlab" => 0xfc6d26, - _ => 0x60a5fa, - }, - }, - _ => return None, - }) -} - -fn default_connection_icon_for_kind(kind: &str) -> ConnectionIconDef { - match kind { - "Local" => ConnectionIconDef { - path: "icons/conn/terminal.svg", - color: 0x4ade80, - }, - "Telnet" => ConnectionIconDef { - path: "icons/conn/telnet.svg", - color: 0xd29922, - }, - "Serial" => ConnectionIconDef { - path: "icons/conn/serial.svg", - color: 0xbc8cff, - }, - _ => ConnectionIconDef { - path: "icons/conn/server.svg", - color: 0x60a5fa, - }, - } -} diff --git a/crates/nyaterm-desktop/src/features/formatting/mod.rs b/crates/nyaterm-desktop/src/features/formatting/mod.rs index 70de70c7..676c8d2a 100644 --- a/crates/nyaterm-desktop/src/features/formatting/mod.rs +++ b/crates/nyaterm-desktop/src/features/formatting/mod.rs @@ -19,9 +19,6 @@ pub(in crate::features) use labels::*; mod ai_history; pub(in crate::features) use ai_history::*; -mod connection_icons; -pub(in crate::features) use connection_icons::*; - mod markdown; pub(in crate::features) use markdown::*; diff --git a/crates/nyaterm-desktop/src/features/icons/aliases.rs b/crates/nyaterm-desktop/src/features/icons/aliases.rs new file mode 100644 index 00000000..4c881752 --- /dev/null +++ b/crates/nyaterm-desktop/src/features/icons/aliases.rs @@ -0,0 +1,117 @@ +//! Key normalization for stored connection icons. +//! +//! Icon keys reach us from three places that spell things differently: the app's +//! own picker (canonical), `.nya` backups written by the old app, and session +//! imports from Xshell / MobaXterm / WindTerm (which is where `Ubuntu.svg`, +//! `kali_linux` and `pwsh` come from). Aliasing is deliberately a *read-time* +//! concern — stored values are never rewritten, so a file that round-trips +//! through an older build still resolves. + +/// Spelling variants accepted for a canonical icon key. Ported verbatim from the +/// pre-GPUI `CONNECTION_ICON_ALIASES`. +pub(super) const CONNECTION_ICON_ALIASES: &[(&str, &str)] = &[ + ("alibaba-cloudlinux", "alibaba-cloud-linux"), + ("alibabacloudlinux", "alibaba-cloud-linux"), + ("alibaba-linux", "alibaba-cloud-linux"), + ("almalinux", "alma"), + ("alma-linux", "alma"), + ("alpine-linux", "alpine"), + ("alpinelinux", "alpine"), + ("anolisos", "anolis"), + ("anolis-os", "anolis"), + ("archlinux", "arch"), + ("arch-linux", "arch"), + ("amazon", "aws"), + ("amazon-linux", "aws"), + ("amazonlinux", "aws"), + ("aws-linux", "aws"), + ("deepin-a", "deepin"), + ("command-prompt", "cmd"), + ("commandprompt", "cmd"), + ("gitbash", "git"), + ("git-bash", "git"), + ("huawei-cloud", "huawei"), + ("huaweicloud", "huawei"), + ("kali-linux", "kali"), + ("kalilinux", "kali"), + ("linuxmint", "mint"), + ("linux-mint", "mint"), + ("nix-os", "nixos"), + ("open-euler", "openeuler"), + ("open-suse", "opensuse"), + ("raspberry", "raspberrypi"), + ("raspberry-pi", "raspberrypi"), + ("rocky-linux", "rocky"), + ("rockylinux", "rocky"), + ("tencent", "tencentos"), + ("tencent-os", "tencentos"), + ("tencentlinux", "tencentos"), + ("ps", "powershell"), + ("pwsh", "powershell"), + ("power-shell", "powershell"), +]; + +/// Fold a stored key toward its canonical spelling: drop a trailing `.svg`, +/// treat `_` as `-`, and lowercase. +pub(super) fn normalize_connection_icon_key(key: &str) -> String { + let trimmed = key.trim(); + let stripped = trimmed + .len() + .checked_sub(4) + .filter(|split| trimmed[*split..].eq_ignore_ascii_case(".svg")) + .map_or(trimmed, |split| &trimmed[..split]); + stripped.replace('_', "-").to_lowercase() +} + +pub(super) fn resolve_alias(normalized: &str) -> &str { + CONNECTION_ICON_ALIASES + .iter() + .find(|(alias, _)| *alias == normalized) + .map_or(normalized, |(_, canonical)| *canonical) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn normalization_strips_the_svg_suffix_case_insensitively() { + assert_eq!(normalize_connection_icon_key("Ubuntu.SVG"), "ubuntu"); + assert_eq!(normalize_connection_icon_key(" Debian.svg "), "debian"); + // A key that merely ends in the same letters must survive intact. + assert_eq!(normalize_connection_icon_key("nosvg"), "nosvg"); + } + + #[test] + fn normalization_folds_underscores_and_case() { + assert_eq!(normalize_connection_icon_key("Kali_Linux"), "kali-linux"); + assert_eq!(normalize_connection_icon_key("ALMA_LINUX"), "alma-linux"); + } + + #[test] + fn every_alias_points_at_a_different_key() { + for (alias, canonical) in CONNECTION_ICON_ALIASES { + assert_ne!(alias, canonical, "{alias} aliases itself"); + assert_eq!( + *alias, + normalize_connection_icon_key(alias), + "{alias} is not in normalized form, so it can never be hit" + ); + } + } + + #[test] + fn aliases_resolve_through_normalization() { + for (input, expected) in [ + ("kali_linux", "kali"), + ("Rocky-Linux.svg", "rocky"), + ("PWSH", "powershell"), + ("AmazonLinux", "aws"), + ] { + assert_eq!( + resolve_alias(&normalize_connection_icon_key(input)), + expected + ); + } + } +} diff --git a/crates/nyaterm-desktop/src/features/icons/connection.rs b/crates/nyaterm-desktop/src/features/icons/connection.rs new file mode 100644 index 00000000..946d3997 --- /dev/null +++ b/crates/nyaterm-desktop/src/features/icons/connection.rs @@ -0,0 +1,418 @@ +//! Connection icon tables, ported from the pre-GPUI `icons.tsx`. +//! +//! Four tables are merged into one lookup, in the same precedence the old app +//! used: server → linux → brand → system, later winning. That ordering is +//! load-bearing, not incidental: eight keys (`apple`, `linux`, `ubuntu`, +//! `debian`, `centos`, `fedora`, `k8s`, `aws`) appear in both the brand table and +//! the system table, and connections must get the full-color OS logo rather than +//! the monochrome brand mark. + +use super::IconDef; +use super::aliases::{normalize_connection_icon_key, resolve_alias}; + +/// Key stored when a connection has no icon of its own. +pub(in crate::features) const DEFAULT_CONNECTION_ICON: &str = "server"; + +/// The default glyph in seven theme-friendly hues. One asset, seven tints — this +/// is exactly what a monochrome alpha mask is for. +const SERVER_ICONS: &[(&str, IconDef)] = &[ + ("server", IconDef::mono("icons/brand/server.svg", 0x60a5fa)), + ( + "server-emerald", + IconDef::mono("icons/brand/server.svg", 0x34d399), + ), + ( + "server-amber", + IconDef::mono("icons/brand/server.svg", 0xfbbf24), + ), + ( + "server-rose", + IconDef::mono("icons/brand/server.svg", 0xfb7185), + ), + ( + "server-violet", + IconDef::mono("icons/brand/server.svg", 0xa78bfa), + ), + ( + "server-cyan", + IconDef::mono("icons/brand/server.svg", 0x22d3ee), + ), + ( + "server-slate", + IconDef::mono("icons/brand/server.svg", 0x94a3b8), + ), +]; + +/// The same seven hues on Tux, for people who want a Linux mark without +/// committing to a distro. +const LINUX_ICONS: &[(&str, IconDef)] = &[ + ( + "linux-default", + IconDef::mono("icons/brand/linux.svg", 0x60a5fa), + ), + ( + "linux-emerald", + IconDef::mono("icons/brand/linux.svg", 0x34d399), + ), + ( + "linux-amber", + IconDef::mono("icons/brand/linux.svg", 0xfbbf24), + ), + ( + "linux-rose", + IconDef::mono("icons/brand/linux.svg", 0xfb7185), + ), + ( + "linux-violet", + IconDef::mono("icons/brand/linux.svg", 0xa78bfa), + ), + ( + "linux-cyan", + IconDef::mono("icons/brand/linux.svg", 0x22d3ee), + ), + ( + "linux-slate", + IconDef::mono("icons/brand/linux.svg", 0x94a3b8), + ), +]; + +/// Service and language marks. Colors are the vendors' published brand values; +/// several are near-black and get lifted at paint time by [`IconDef::tint`]. +pub(super) const BRAND_ICONS: &[(&str, IconDef)] = &[ + ("docker", IconDef::mono("icons/brand/docker.svg", 0x2496ed)), + ("k8s", IconDef::mono("icons/brand/kubernetes.svg", 0x326ce5)), + ("linux", IconDef::mono("icons/brand/linux.svg", 0xfcc624)), + ("ubuntu", IconDef::mono("icons/brand/ubuntu.svg", 0xe95420)), + ("debian", IconDef::mono("icons/brand/debian.svg", 0xa81d33)), + ("centos", IconDef::mono("icons/brand/centos.svg", 0x262577)), + ("fedora", IconDef::mono("icons/brand/fedora.svg", 0x3c4fb1)), + ("apple", IconDef::mono("icons/brand/apple.svg", 0xa2aaad)), + ("github", IconDef::mono("icons/brand/github.svg", 0x181717)), + ("gitlab", IconDef::mono("icons/brand/gitlab.svg", 0xfc6d26)), + ("nginx", IconDef::mono("icons/brand/nginx.svg", 0x009639)), + ("redis", IconDef::mono("icons/brand/redis.svg", 0xdc382d)), + ( + "postgres", + IconDef::mono("icons/brand/postgresql.svg", 0x4169e1), + ), + ("mysql", IconDef::mono("icons/brand/mysql.svg", 0x4479a1)), + ( + "mongodb", + IconDef::mono("icons/brand/mongodb.svg", 0x47a248), + ), + ("python", IconDef::mono("icons/brand/python.svg", 0x3776ab)), + ("js", IconDef::mono("icons/brand/javascript.svg", 0xf7df1e)), + ("ts", IconDef::mono("icons/brand/typescript.svg", 0x3178c6)), + ("rust", IconDef::mono("icons/brand/rust.svg", 0x000000)), + ("go", IconDef::mono("icons/brand/go.svg", 0x00add8)), + ("node", IconDef::mono("icons/brand/nodedotjs.svg", 0x339933)), + ("php", IconDef::mono("icons/brand/php.svg", 0x777bb4)), + ("aws", IconDef::mono("icons/brand/aws.svg", 0x232f3e)), + ( + "gcp", + IconDef::mono("icons/brand/googlecloud.svg", 0x4285f4), + ), +]; + +/// Official OS and distro logos, kept in full color. These override the brand +/// table on the eight colliding keys. +const SYSTEM_ICONS: &[(&str, IconDef)] = &[ + ("windows", IconDef::full_color("color/os/windows.svg")), + ("apple", IconDef::full_color("color/os/apple.svg")), + ("android", IconDef::full_color("color/os/android.svg")), + ("linux", IconDef::full_color("color/os/linux.svg")), + ("ubuntu", IconDef::full_color("color/os/ubuntu.svg")), + ("debian", IconDef::full_color("color/os/debian.svg")), + ("centos", IconDef::full_color("color/os/centos.svg")), + ("fedora", IconDef::full_color("color/os/fedora.svg")), + ("arch", IconDef::full_color("color/os/archlinux.svg")), + ("manjaro", IconDef::full_color("color/os/manjaro.svg")), + ("opensuse", IconDef::full_color("color/os/opensuse.svg")), + ("rocky", IconDef::full_color("color/os/rocky-linux.svg")), + ("alma", IconDef::full_color("color/os/almalinux.svg")), + ("alpine", IconDef::full_color("color/os/alpine-linux.svg")), + ("kali", IconDef::full_color("color/os/kalilinux.svg")), + ("mint", IconDef::full_color("color/os/linux-mint.svg")), + ("nixos", IconDef::full_color("color/os/nixos.svg")), + ("h3c", IconDef::full_color("color/os/h3c.svg")), + ("k8s", IconDef::full_color("color/os/k8s.svg")), + ("gentoo", IconDef::full_color("color/os/gentoo.svg")), + ( + "raspberrypi", + IconDef::full_color("color/os/raspberrypi.svg"), + ), + ( + "alibaba-cloud-linux", + IconDef::full_color("color/os/alibabacloudlinux.svg"), + ), + ("anolis", IconDef::full_color("color/os/anolisos.svg")), + ("deepin", IconDef::full_color("color/os/deepin.svg")), + ("kylin", IconDef::full_color("color/os/kylin.svg")), + ("openeuler", IconDef::full_color("color/os/openeuler.svg")), + ("tencentos", IconDef::full_color("color/os/tencentos.svg")), + ("uos", IconDef::full_color("color/os/uos.svg")), + ("aws", IconDef::full_color("color/os/aws.svg")), + ("huawei", IconDef::full_color("color/os/huawei.svg")), + ("git", IconDef::full_color("color/os/git.svg")), + ("cmd", IconDef::full_color("color/os/cmd.svg")), + ("powershell", IconDef::full_color("color/os/powershell.svg")), +]; + +/// Keys the GPUI build has written that the old app never had. They are not +/// offered in the picker, but stored connections already carry them, so they must +/// keep resolving. Do not prune this list without a data migration. +const LEGACY_ICONS: &[(&str, IconDef)] = &[ + ("kubernetes", IconDef::full_color("color/os/k8s.svg")), + ("local", IconDef::mono("icons/conn/terminal.svg", 0x4ade80)), + ( + "terminal", + IconDef::mono("icons/conn/terminal.svg", 0x4ade80), + ), + ("telnet", IconDef::mono("icons/conn/telnet.svg", 0xd29922)), + ("serial", IconDef::mono("icons/conn/serial.svg", 0xbc8cff)), + ("folder", IconDef::mono("icons/conn/folder.svg", 0xfbbf24)), + ("group", IconDef::mono("icons/conn/folder.svg", 0xfbbf24)), + // The old app had no FreeBSD entry; the GPUI picker offered one backed by the + // generic Tux glyph. Keep it resolving for anyone who picked it. + ("freebsd", IconDef::mono("icons/brand/linux.svg", 0xab2b28)), +]; + +/// Picker contents, in the old app's display order: the seven server hues, the +/// seven Linux hues, then every OS logo. +pub(in crate::features) fn connection_icon_options() -> impl Iterator { + SERVER_ICONS + .iter() + .chain(LINUX_ICONS) + .chain(SYSTEM_ICONS) + .map(|(key, _)| *key) +} + +/// Materialized picker list, so grid code can index and count without collecting. +pub(in crate::features) static CONNECTION_ICON_OPTIONS: std::sync::LazyLock> = + std::sync::LazyLock::new(|| connection_icon_options().collect()); + +fn lookup(key: &str) -> Option { + // Reverse precedence order: the table that wins is consulted first. + for table in [ + SYSTEM_ICONS, + BRAND_ICONS, + LINUX_ICONS, + SERVER_ICONS, + LEGACY_ICONS, + ] { + if let Some((_, def)) = table.iter().find(|(candidate, _)| *candidate == key) { + return Some(*def); + } + } + None +} + +/// Resolve a stored icon key, falling back to a glyph for the connection kind. +/// +/// Matches the old `resolveConnectionIcon` exactly: an exact key wins first, then +/// the normalized-and-aliased form, then the default. Step one is byte-exact on +/// purpose — normalizing up front would let `Ubuntu` and `ubuntu` diverge if a +/// case-sensitive key is ever added. +pub(in crate::features) fn resolve_connection_icon(icon_key: Option<&str>, kind: &str) -> IconDef { + if let Some(key) = icon_key.filter(|key| !key.is_empty()) { + if let Some(def) = lookup(key) { + return def; + } + let normalized = normalize_connection_icon_key(key); + if let Some(def) = lookup(resolve_alias(&normalized)) { + return def; + } + } + default_connection_icon_for_kind(kind) +} + +/// Kind-specific default. The old app always fell back to the blue server glyph; +/// giving Local/Telnet/Serial their own mark is an intentional improvement, and +/// it is what drives the session tab icons. +pub(in crate::features) fn default_connection_icon_for_kind(kind: &str) -> IconDef { + match kind { + "Local" => IconDef::mono("icons/conn/terminal.svg", 0x4ade80), + "Telnet" => IconDef::mono("icons/conn/telnet.svg", 0xd29922), + "Serial" => IconDef::mono("icons/conn/serial.svg", 0xbc8cff), + _ => IconDef::mono("icons/brand/server.svg", 0x60a5fa), + } +} + +#[cfg(test)] +mod tests { + use super::super::IconPaint; + use super::super::aliases::CONNECTION_ICON_ALIASES; + use super::*; + + /// Every key the GPUI picker has ever offered. Stored connections carry these + /// verbatim, so none may stop resolving. + const PREVIOUSLY_OFFERED_KEYS: &[&str] = &[ + "server", + "server-emerald", + "server-amber", + "server-rose", + "server-violet", + "server-cyan", + "server-slate", + "windows", + "apple", + "android", + "linux", + "ubuntu", + "debian", + "centos", + "fedora", + "arch", + "manjaro", + "opensuse", + "rocky", + "alma", + "alpine", + "kali", + "mint", + "nixos", + "gentoo", + "freebsd", + "raspberrypi", + // Written by code paths rather than the picker. + "local", + "terminal", + "telnet", + "serial", + "folder", + "group", + "docker", + "python", + "github", + "kubernetes", + "k8s", + "nginx", + "redis", + "postgres", + "mysql", + "mongodb", + "js", + "ts", + "rust", + "go", + "node", + "php", + "aws", + "gcp", + "gitlab", + ]; + + fn resolves(key: &str) -> bool { + resolve_connection_icon(Some(key), "SSH") != default_connection_icon_for_kind("SSH") + || key == "server" + } + + #[test] + fn every_previously_stored_key_still_resolves() { + for key in PREVIOUSLY_OFFERED_KEYS { + assert!( + resolves(key), + "{key} fell through to the default icon; stored connections would \ + silently lose their icon" + ); + } + } + + #[test] + fn every_alias_resolves_to_a_real_icon() { + for (alias, canonical) in CONNECTION_ICON_ALIASES { + assert!( + lookup(canonical).is_some(), + "alias {alias} points at {canonical}, which is not in any table" + ); + assert_eq!( + resolve_connection_icon(Some(alias), "SSH"), + resolve_connection_icon(Some(canonical), "SSH"), + ); + } + } + + #[test] + fn system_logos_win_over_brand_marks_on_shared_keys() { + for key in [ + "apple", "linux", "ubuntu", "debian", "centos", "fedora", "k8s", "aws", + ] { + let def = resolve_connection_icon(Some(key), "SSH"); + assert_eq!( + def.paint, + IconPaint::FullColor, + "{key} must use the full-color OS logo, not the brand mark" + ); + } + } + + #[test] + fn distros_no_longer_share_one_glyph() { + // The whole point of the change: these all pointed at conn/linux.svg and + // differed only by tint. + let distros = [ + "centos", + "fedora", + "arch", + "manjaro", + "opensuse", + "rocky", + "alma", + "alpine", + "kali", + "mint", + "nixos", + "gentoo", + "raspberrypi", + ]; + let mut paths: Vec<_> = distros + .iter() + .map(|key| resolve_connection_icon(Some(key), "SSH").path) + .collect(); + paths.sort_unstable(); + let total = paths.len(); + paths.dedup(); + assert_eq!(paths.len(), total, "some distros still share an asset"); + } + + #[test] + fn kubernetes_is_not_a_whale() { + let k8s = resolve_connection_icon(Some("k8s"), "SSH"); + let docker = resolve_connection_icon(Some("docker"), "SSH"); + assert_ne!(k8s.path, docker.path); + } + + #[test] + fn unknown_and_empty_keys_fall_back_by_kind() { + let default = default_connection_icon_for_kind("SSH"); + assert_eq!(resolve_connection_icon(None, "SSH"), default); + assert_eq!(resolve_connection_icon(Some(""), "SSH"), default); + assert_eq!(resolve_connection_icon(Some(" "), "SSH"), default); + assert_eq!(resolve_connection_icon(Some("nonsense"), "SSH"), default); + assert_eq!( + resolve_connection_icon(None, "Serial"), + default_connection_icon_for_kind("Serial"), + ); + } + + #[test] + fn picker_lists_every_hue_and_every_logo_without_duplicates() { + let options = &*CONNECTION_ICON_OPTIONS; + assert_eq!( + options.len(), + SERVER_ICONS.len() + LINUX_ICONS.len() + SYSTEM_ICONS.len() + ); + let mut sorted = options.clone(); + sorted.sort_unstable(); + let total = sorted.len(); + sorted.dedup(); + assert_eq!(sorted.len(), total, "the picker offers a key twice"); + + for key in options { + assert!( + lookup(key).is_some(), + "picker offers unresolvable key {key}" + ); + } + } +} diff --git a/crates/nyaterm-desktop/src/features/icons/file_kind.rs b/crates/nyaterm-desktop/src/features/icons/file_kind.rs new file mode 100644 index 00000000..55326240 --- /dev/null +++ b/crates/nyaterm-desktop/src/features/icons/file_kind.rs @@ -0,0 +1,188 @@ +//! File-explorer entry icons, a port of the old `getFileIcon`. +//! +//! Resolution order is load-bearing: symlink, then directory, then extension, +//! then a dotfile check, then the generic document. + +use super::IconDef; +use crate::theme::ThemePalette; + +/// Extension groups sharing one icon. Ordered as in the old table so a reviewer +/// can diff the two side by side. +const EXTENSION_ICONS: &[(&[&str], &str, u32)] = &[ + (&["js", "jsx"], "icons/brand/javascript.svg", 0xfacc15), + (&["ts", "tsx"], "icons/brand/typescript.svg", 0x60a5fa), + (&["html", "htm"], "icons/brand/html5.svg", 0xf97316), + (&["css", "scss", "less"], "icons/brand/css.svg", 0x38bdf8), + (&["py", "pyc"], "icons/brand/python.svg", 0x3776ab), + ( + &["sh", "bash", "zsh", "bat", "ps1"], + "icons/conn/terminal.svg", + 0x4ade80, + ), + (&["php"], "icons/brand/php.svg", 0x777bb4), + ( + &["rs", "go", "c", "cpp", "java"], + "icons/file/code.svg", + 0xf87171, + ), + ( + &["json", "yaml", "yml", "toml", "xml"], + "icons/file/data-object.svg", + 0xa78bfa, + ), + (&["sql", "db", "sqlite"], "icons/file/storage.svg", 0x94a3b8), + (&["doc", "docx"], "icons/file/description.svg", 0x3b82f6), + (&["pdf"], "icons/file/pdf.svg", 0xef4444), + (&["xls", "xlsx", "csv"], "icons/file/table.svg", 0x16a34a), + (&["ppt", "pptx"], "icons/file/present.svg", 0xea580c), + ( + &["png", "jpg", "jpeg", "gif", "webp", "svg", "ico"], + "icons/file/image.svg", + 0xec4899, + ), + ( + &["mp4", "mkv", "avi", "mov", "webm"], + "icons/file/movie.svg", + 0x8b5cf6, + ), + ( + &["mp3", "wav", "ogg", "flac"], + "icons/file/audio.svg", + 0xf59e0b, + ), + ( + &["zip", "rar", "7z", "tar", "gz", "bz2", "xz"], + "icons/file/archive.svg", + 0xf59e0b, + ), + ( + &["exe", "apk", "dmg", "iso"], + "icons/file/apps.svg", + 0x14b8a6, + ), +]; + +/// Extensions whose icon is themed rather than branded, so they recede instead of +/// competing with the colored ones. +fn muted_extension_icon(extension: &str, palette: ThemePalette) -> Option { + match extension { + "ini" | "env" | "conf" | "config" => { + Some(IconDef::mono("icons/settings.svg", palette.text_muted)) + } + "md" | "mdx" | "txt" | "rtf" => { + Some(IconDef::mono("icons/file/text.svg", palette.text_dimmed)) + } + "lock" => Some(IconDef::mono("icons/lock.svg", palette.text_muted)), + _ => None, + } +} + +/// Icon for one file-browser row. +pub(in crate::features) fn file_entry_icon( + name: &str, + is_directory: bool, + is_symlink: bool, + palette: ThemePalette, +) -> IconDef { + if is_symlink { + return IconDef::mono("icons/conn/symlink.svg", 0x67e8f9); + } + if is_directory { + return IconDef::mono("icons/conn/folder.svg", 0xfbbf24); + } + + // Only a name that actually contains a dot has an extension. Without this, + // `Makefile` would "extend" to `makefile` and miss the dotfile branch below. + if let Some(extension) = name + .contains('.') + .then(|| name.rsplit('.').next().unwrap_or_default().to_lowercase()) + { + if let Some(def) = muted_extension_icon(&extension, palette) { + return def; + } + if let Some((_, path, color)) = EXTENSION_ICONS + .iter() + .find(|(extensions, _, _)| extensions.contains(&extension.as_str())) + { + return IconDef::mono(path, *color); + } + } + + // Dotfiles are configuration by convention, even without a known extension. + if name.starts_with('.') { + return IconDef::mono("icons/settings.svg", palette.text_muted); + } + IconDef::mono("icons/conn/file.svg", palette.text_muted) +} + +#[cfg(test)] +mod tests { + use super::*; + + fn palette() -> ThemePalette { + crate::theme::theme_palette("github-dark") + } + + fn icon_for(name: &str) -> IconDef { + file_entry_icon(name, false, false, palette()) + } + + #[test] + fn directories_and_symlinks_win_over_any_extension() { + let palette = palette(); + assert_eq!( + file_entry_icon("archive.zip", true, false, palette).path, + "icons/conn/folder.svg" + ); + assert_eq!( + file_entry_icon("shortcut.png", false, true, palette).path, + "icons/conn/symlink.svg" + ); + // A symlinked directory reads as a link, matching the old ordering. + assert_eq!( + file_entry_icon("link", true, true, palette).path, + "icons/conn/symlink.svg" + ); + } + + #[test] + fn extensions_are_matched_case_insensitively() { + assert_eq!(icon_for("Photo.JPEG").path, "icons/file/image.svg"); + assert_eq!(icon_for("Main.RS").path, "icons/file/code.svg"); + } + + #[test] + fn extensionless_names_do_not_borrow_their_own_name() { + // `Makefile`.rsplit('.') yields "Makefile", which must not be treated as + // an extension. + assert_eq!(icon_for("Makefile").path, "icons/conn/file.svg"); + assert_eq!(icon_for("LICENSE").path, "icons/conn/file.svg"); + } + + #[test] + fn dotfiles_read_as_configuration() { + assert_eq!(icon_for(".bashrc").path, "icons/settings.svg"); + assert_eq!(icon_for(".gitignore").path, "icons/settings.svg"); + // A dotfile with a known extension still gets the specific icon. + assert_eq!( + icon_for(".eslintrc.json").path, + "icons/file/data-object.svg" + ); + } + + #[test] + fn known_kinds_do_not_collapse_onto_the_generic_document() { + let generic = icon_for("unknown.qqq").path; + for name in [ + "a.js", "a.ts", "a.html", "a.css", "a.py", "a.sh", "a.php", "a.rs", "a.json", "a.ini", + "a.sql", "a.md", "a.doc", "a.pdf", "a.csv", "a.pptx", "a.png", "a.mp4", "a.mp3", + "a.zip", "a.iso", "a.lock", + ] { + assert_ne!( + icon_for(name).path, + generic, + "{name} has no icon of its own" + ); + } + } +} diff --git a/crates/nyaterm-desktop/src/features/icons/mod.rs b/crates/nyaterm-desktop/src/features/icons/mod.rs new file mode 100644 index 00000000..a81fae5c --- /dev/null +++ b/crates/nyaterm-desktop/src/features/icons/mod.rs @@ -0,0 +1,164 @@ +//! Icon lookup tables. +//! +//! This is the data half of the icon system; the element half lives in +//! [`crate::features::view_widgets::icons`]. Keeping them apart is what stops a +//! ~1000-line brand table from being rebuilt inside a render closure. +//! +//! The tables are a port of the pre-GPUI `src/components/icons.tsx`. Their keys +//! are **persisted user data** — `SavedConnection::icon` and +//! `QuickCommand::icon_tag` are free-form strings written by both the old and the +//! new app and carried in `.nya` backups — so keys may be added but never +//! repurposed or removed. + +mod aliases; +mod connection; +mod file_kind; +mod quick; +mod remote_system; +mod search; + +pub(in crate::features) use connection::{ + CONNECTION_ICON_OPTIONS, DEFAULT_CONNECTION_ICON, resolve_connection_icon, +}; +pub(in crate::features) use file_kind::file_entry_icon; +pub(in crate::features) use quick::{QUICK_COMMAND_ICON_OPTIONS, quick_command_icon}; +pub(in crate::features) use remote_system::infer_connection_icon_key_from_remote_system; +pub(in crate::features) use search::{SEARCH_ENGINE_ICON_IDS, search_engine_icon}; + +use crate::theme::ThemePalette; + +/// How an icon asset must be painted. +/// +/// The two GPUI elements are not interchangeable: `svg()` reduces the asset to an +/// alpha mask (so one glyph serves seven tints, but a multi-color logo is lost), +/// while `img()` keeps the pixels (so a distro logo survives, but nothing can +/// tint it). The path prefix encodes the same split — `icons/**` vs `color/**`. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub(in crate::features) enum IconPaint { + /// Monochrome mask, tinted with this vendor brand color. + Mono(u32), + /// Full-color raster, painted as authored. The old UI rendered these through + /// an `` whose `color` was inert, so losing the tint is parity, not loss. + FullColor, +} + +/// A resolved icon: which asset, and how to paint it. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub(in crate::features) struct IconDef { + pub path: &'static str, + pub paint: IconPaint, +} + +impl IconDef { + pub(in crate::features) const fn mono(path: &'static str, color: u32) -> Self { + Self { + path, + paint: IconPaint::Mono(color), + } + } + + pub(in crate::features) const fn full_color(path: &'static str) -> Self { + Self { + path, + paint: IconPaint::FullColor, + } + } + + /// Tint for a monochrome icon, lifted so it stays legible on the current + /// surface. Full-color icons return `None` — they are painted as authored. + /// + /// Several published brand colors are near-black (`rust` is literally + /// `#000000`, `github` `#181717`), which disappears on a dark surface. The old + /// UI shipped that bug; rather than corrupting the vendor values in the table, + /// the correction happens here so light themes still get the true color and + /// any brand added later is covered automatically. + pub(in crate::features) fn tint(self, palette: ThemePalette) -> Option { + match self.paint { + IconPaint::Mono(color) => Some(legible_on(color, palette)), + IconPaint::FullColor => None, + } + } +} + +/// Relative luminance (WCAG), used only to decide whether a brand color has +/// collapsed into the background. +fn relative_luminance(color: u32) -> f32 { + fn channel(value: u32) -> f32 { + let value = (value & 0xff) as f32 / 255.0; + if value <= 0.03928 { + value / 12.92 + } else { + ((value + 0.055) / 1.055).powf(2.4) + } + } + 0.2126 * channel(color >> 16) + 0.7152 * channel(color >> 8) + 0.0722 * channel(color) +} + +fn blend(from: u32, to: u32, amount: f32) -> u32 { + let mix = |shift: u32| { + let a = ((from >> shift) & 0xff) as f32; + let b = ((to >> shift) & 0xff) as f32; + (a + (b - a) * amount).round().clamp(0.0, 255.0) as u32 + }; + (mix(16) << 16) | (mix(8) << 8) | mix(0) +} + +/// Contrast ratio floor below which a brand color is blended toward the theme's +/// text color. 1.6 is deliberately low: it rescues `#000000` on a dark surface +/// without washing out colors that merely look dim, such as `debian`'s `#a81d33`. +const MIN_CONTRAST_RATIO: f32 = 1.6; + +fn legible_on(color: u32, palette: ThemePalette) -> u32 { + let icon = relative_luminance(color); + let surface = relative_luminance(palette.surface); + let (lighter, darker) = if icon > surface { + (icon, surface) + } else { + (surface, icon) + }; + let ratio = (lighter + 0.05) / (darker + 0.05); + if ratio >= MIN_CONTRAST_RATIO { + return color; + } + // Pull toward the theme's own text color rather than a fixed white/black, so + // the lift lands inside the palette instead of next to it. + blend(color, palette.text, 0.65) +} + +#[cfg(test)] +mod tests { + use super::*; + + fn dark() -> ThemePalette { + crate::theme::theme_palette("github-dark") + } + + #[test] + fn near_black_brand_colors_are_lifted_off_a_dark_surface() { + let palette = dark(); + // simple-icons publishes Rust as pure black; unlifted it is invisible. + let lifted = legible_on(0x000000, palette); + assert_ne!(lifted, 0x000000); + assert!(relative_luminance(lifted) > relative_luminance(palette.surface)); + } + + #[test] + fn legible_brand_colors_are_left_alone() { + let palette = dark(); + for color in [0x2496ed, 0xe95420, 0xfcc624, 0x326ce5] { + assert_eq!( + legible_on(color, palette), + color, + "{color:#08x} reads fine and must keep its published value" + ); + } + } + + #[test] + fn full_color_icons_report_no_tint() { + assert_eq!( + IconDef::full_color("color/os/ubuntu.svg").tint(dark()), + None + ); + } +} diff --git a/crates/nyaterm-desktop/src/features/icons/quick.rs b/crates/nyaterm-desktop/src/features/icons/quick.rs new file mode 100644 index 00000000..c6e27a46 --- /dev/null +++ b/crates/nyaterm-desktop/src/features/icons/quick.rs @@ -0,0 +1,106 @@ +//! Quick-command icon tags. +//! +//! The old app offered exactly the 24 brand marks; the GPUI build added six +//! generic ones and has already written them into saved commands, so they stay. + +use super::IconDef; +use super::connection::BRAND_ICONS; + +/// Generic tags with no counterpart in the old app. Listed after the brand marks +/// so the picker reads brand-first, matching the old grid. +const GENERIC_ICONS: &[(&str, IconDef)] = &[ + ( + "terminal", + IconDef::mono("icons/conn/terminal.svg", 0x4ade80), + ), + ("code", IconDef::mono("icons/file/code.svg", 0x58a6ff)), + ("server", IconDef::mono("icons/brand/server.svg", 0x60a5fa)), + ("folder", IconDef::mono("icons/conn/folder.svg", 0xfbbf24)), + ("sparkles", IconDef::mono("icons/ai.svg", 0xa78bfa)), + ("bolt", IconDef::mono("icons/commands.svg", 0xfbbf24)), +]; + +/// Picker contents: `None` clears the icon and falls back to the color dot. +pub(in crate::features) fn quick_command_icon_options() -> impl Iterator> +{ + std::iter::once(None).chain( + BRAND_ICONS + .iter() + .chain(GENERIC_ICONS) + .map(|(key, _)| Some(*key)), + ) +} + +pub(in crate::features) static QUICK_COMMAND_ICON_OPTIONS: std::sync::LazyLock< + Vec>, +> = std::sync::LazyLock::new(|| quick_command_icon_options().collect()); + +/// Resolve a stored `icon_tag`. Unlike connection icons these are always +/// monochrome — the old app rendered them as a react-icons glyph plus a CSS +/// color, which is exactly what an alpha mask reproduces. +pub(in crate::features) fn quick_command_icon(icon_tag: &str) -> Option { + let key = icon_tag.trim().to_ascii_lowercase(); + BRAND_ICONS + .iter() + .chain(GENERIC_ICONS) + .find(|(candidate, _)| *candidate == key) + .map(|(_, def)| *def) +} + +#[cfg(test)] +mod tests { + use super::super::IconPaint; + use super::*; + + /// Tags the GPUI picker has offered since the migration. Saved commands carry + /// them, so none may stop resolving. + const PREVIOUSLY_OFFERED_TAGS: &[&str] = &[ + "terminal", "code", "server", "folder", "sparkles", "bolt", "docker", "k8s", "linux", + "ubuntu", "debian", "centos", "fedora", "apple", "github", "gitlab", "nginx", "redis", + "postgres", "mysql", "mongodb", "python", "js", "ts", "rust", "go", "node", "php", "aws", + "gcp", + ]; + + #[test] + fn every_previously_offered_tag_still_resolves() { + for tag in PREVIOUSLY_OFFERED_TAGS { + assert!( + quick_command_icon(tag).is_some(), + "{tag} no longer resolves; saved commands would lose their icon" + ); + } + } + + #[test] + fn tags_are_matched_case_insensitively_and_trimmed() { + assert_eq!(quick_command_icon(" Docker "), quick_command_icon("docker")); + assert!(quick_command_icon("not-a-tag").is_none()); + } + + #[test] + fn quick_command_icons_are_all_monochrome() { + // These render inside small list rows where the icon has to pick up the + // row's selected/dimmed treatment, so a full-color asset would look wrong. + for tag in PREVIOUSLY_OFFERED_TAGS { + let def = quick_command_icon(tag).expect("tag resolves"); + assert!( + matches!(def.paint, IconPaint::Mono(_)), + "{tag} resolved to a full-color asset" + ); + assert!( + def.path.starts_with("icons/"), + "{tag} points outside icons/" + ); + } + } + + #[test] + fn picker_offers_a_clear_option_then_every_tag() { + let options = &*QUICK_COMMAND_ICON_OPTIONS; + assert_eq!(options.first().copied(), Some(None)); + assert_eq!(options.len(), 1 + BRAND_ICONS.len() + GENERIC_ICONS.len()); + for tag in options.iter().flatten() { + assert!(quick_command_icon(tag).is_some()); + } + } +} diff --git a/crates/nyaterm-desktop/src/features/icons/remote_system.rs b/crates/nyaterm-desktop/src/features/icons/remote_system.rs new file mode 100644 index 00000000..876af245 --- /dev/null +++ b/crates/nyaterm-desktop/src/features/icons/remote_system.rs @@ -0,0 +1,198 @@ +//! Guess a connection's icon from what the remote host reports about itself. +//! +//! Port of the old `inferConnectionIconKeyFromRemoteSystem`. Kept as a pure +//! string function so it can run wherever the stats snapshot arrives, including +//! off the render thread. + +/// Distro needles in priority order. First match wins, so the more specific +/// entries have to come before the generic ones — `alibaba cloud linux` must be +/// tested before the bare `linux` fallback at the bottom of [`infer`]. +const DISTRO_MATCHES: &[(&[&str], &str)] = &[ + ( + &[ + "alibaba cloud linux", + "alibaba-cloud-linux", + "aliyun linux", + "alinux", + ], + "alibaba-cloud-linux", + ), + (&["amazon linux", "amzn", "aws linux"], "aws"), + (&["alma linux", "almalinux"], "alma"), + (&["alpine"], "alpine"), + (&["anolis"], "anolis"), + (&["arch linux", "arch-linux", "archlinux", "arch"], "arch"), + (&["centos", "cent os"], "centos"), + (&["debian"], "debian"), + (&["deepin"], "deepin"), + (&["fedora"], "fedora"), + (&["huawei", "opencloudos"], "huawei"), + (&["kali"], "kali"), + (&["kylin"], "kylin"), + (&["linux mint", "linuxmint"], "mint"), + (&["nixos", "nix os"], "nixos"), + (&["open euler", "openeuler"], "openeuler"), + (&["opensuse", "open suse", "sles", "suse"], "opensuse"), + (&["rocky"], "rocky"), + (&["tencent", "tlinux"], "tencentos"), + (&["ubuntu"], "ubuntu"), + (&["uniontech", "uos"], "uos"), +]; + +fn normalize(os: &str, arch: &str) -> String { + let joined = format!("{os} {arch}"); + let mut out = String::with_capacity(joined.len()); + let mut pending_space = false; + for ch in joined.chars() { + let ch = match ch { + '_' | '.' | '/' => '-', + _ => ch, + }; + if ch.is_whitespace() { + pending_space = !out.is_empty(); + continue; + } + if pending_space { + out.push(' '); + pending_space = false; + } + out.extend(ch.to_lowercase()); + } + out +} + +/// Whole-word match, so `arch` does not fire on `search` or `aarch64`. +fn has_token(text: &str, token: &str) -> bool { + let is_word_char = |ch: char| ch.is_ascii_alphanumeric(); + text.match_indices(token).any(|(start, matched)| { + let before_ok = text[..start] + .chars() + .next_back() + .is_none_or(|ch| !is_word_char(ch)); + let after_ok = text[start + matched.len()..] + .chars() + .next() + .is_none_or(|ch| !is_word_char(ch)); + before_ok && after_ok + }) +} + +/// Alphanumeric needles are token-matched; anything containing a separator is a +/// plain substring test. This is per-needle, not per-group, so `alinux` is +/// token-matched while its sibling `alibaba cloud linux` is not. +fn matches_needle(text: &str, needle: &str) -> bool { + if needle.chars().all(|ch| ch.is_ascii_alphanumeric()) { + has_token(text, needle) + } else { + text.contains(needle) + } +} + +/// Map a remote host's reported OS/arch to a connection icon key. +pub(in crate::features) fn infer_connection_icon_key_from_remote_system( + os: &str, + arch: &str, +) -> Option<&'static str> { + let text = normalize(os, arch); + if text.is_empty() { + return None; + } + + for (needles, icon_key) in DISTRO_MATCHES { + if needles.iter().any(|needle| matches_needle(&text, needle)) { + return Some(icon_key); + } + } + + if text.contains("windows") || text.contains("mingw") || text.contains("msys") { + return Some("windows"); + } + if text.contains("darwin") + || text.contains("macos") + || text.contains("mac os") + || text.contains("os x") + { + return Some("apple"); + } + if text.contains("linux") || text.contains("gnu") { + return Some("linux"); + } + None +} + +#[cfg(test)] +mod tests { + use super::super::connection::resolve_connection_icon; + use super::super::{IconPaint, connection::default_connection_icon_for_kind}; + use super::*; + + fn infer(os: &str) -> Option<&'static str> { + infer_connection_icon_key_from_remote_system(os, "x86_64") + } + + #[test] + fn common_uname_strings_land_on_the_right_distro() { + assert_eq!(infer("Ubuntu 24.04.1 LTS"), Some("ubuntu")); + assert_eq!(infer("CentOS Linux 7"), Some("centos")); + assert_eq!(infer("Debian GNU/Linux 12"), Some("debian")); + assert_eq!(infer("Rocky Linux 9.4"), Some("rocky")); + assert_eq!(infer("openEuler 24.03"), Some("openeuler")); + assert_eq!(infer("Alibaba Cloud Linux 3"), Some("alibaba-cloud-linux")); + assert_eq!(infer("Amazon Linux 2023"), Some("aws")); + assert_eq!(infer("Kylin V10"), Some("kylin")); + assert_eq!(infer("UnionTech OS Server 20"), Some("uos")); + } + + #[test] + fn specific_distros_beat_the_generic_linux_fallback() { + // Every one of these also contains "linux". + assert_eq!(infer("Alpine Linux v3.20"), Some("alpine")); + assert_eq!(infer("Kali GNU/Linux Rolling"), Some("kali")); + assert_eq!(infer("Linux Mint 22"), Some("mint")); + } + + #[test] + fn unrecognized_unix_still_reports_something_useful() { + assert_eq!(infer("Linux 6.8.0-generic"), Some("linux")); + assert_eq!(infer("Darwin"), Some("apple")); + assert_eq!(infer("MINGW64_NT-10.0"), Some("windows")); + } + + #[test] + fn arch_matching_respects_word_boundaries() { + // `aarch64` must not read as Arch Linux. + assert_eq!( + infer_connection_icon_key_from_remote_system("Ubuntu 24.04", "aarch64"), + Some("ubuntu") + ); + assert_eq!( + infer_connection_icon_key_from_remote_system("Linux", "aarch64"), + Some("linux") + ); + assert_eq!(infer("Arch Linux"), Some("arch")); + } + + #[test] + fn empty_input_infers_nothing() { + assert_eq!(infer_connection_icon_key_from_remote_system("", ""), None); + assert_eq!( + infer_connection_icon_key_from_remote_system(" ", " "), + None + ); + assert_eq!(infer("Plan 9"), None); + } + + #[test] + fn every_inferred_key_resolves_to_a_real_icon() { + let generic = default_connection_icon_for_kind("SSH"); + for (_, icon_key) in DISTRO_MATCHES { + let def = resolve_connection_icon(Some(icon_key), "SSH"); + assert_ne!(def, generic, "inferred key {icon_key} has no icon"); + assert_eq!( + def.paint, + IconPaint::FullColor, + "inferred key {icon_key} should land on an OS logo" + ); + } + } +} diff --git a/crates/nyaterm-desktop/src/features/icons/search.rs b/crates/nyaterm-desktop/src/features/icons/search.rs new file mode 100644 index 00000000..a1b7a1b7 --- /dev/null +++ b/crates/nyaterm-desktop/src/features/icons/search.rs @@ -0,0 +1,86 @@ +//! Search-engine icons for the terminal selection search menu and its settings. + +use super::IconDef; +use crate::theme::ThemePalette; + +/// Engines offered by the settings picker, in display order. `default` is the +/// catch-all for a custom engine with no icon chosen. +pub(in crate::features) const SEARCH_ENGINE_ICON_IDS: &[&str] = &[ + "google", + "bing", + "duckduckgo", + "github", + "gitlab", + "baidu", + "yahoo", + "youtube", + "bilibili", + "zhihu", + "openai", + "claude", + "gemini", + "default", +]; + +const SEARCH_ICONS: &[(&str, &str, u32)] = &[ + ("google", "icons/brand/google.svg", 0x4285f4), + // Bing has no mark of its own in the icon sets we vendor; its parent brand is + // the closest recognizable stand-in, and it stays distinct from `default`. + ("bing", "icons/brand/bing.svg", 0x008373), + ("duckduckgo", "icons/brand/duckduckgo.svg", 0xde5833), + ("github", "icons/brand/github.svg", 0x181717), + ("gitlab", "icons/brand/gitlab.svg", 0xfc6d26), + ("baidu", "icons/brand/baidu.svg", 0x2932e1), + ("yahoo", "icons/brand/yahoo.svg", 0x410093), + ("youtube", "icons/brand/youtube.svg", 0xff0000), + ("bilibili", "icons/brand/bilibili.svg", 0x00a1d6), + ("zhihu", "icons/brand/zhihu.svg", 0x0084ff), + ("openai", "icons/brand/openai.svg", 0x10a37f), + ("claude", "icons/brand/claude.svg", 0xd97757), + ("gemini", "icons/brand/gemini.svg", 0x4285f4), +]; + +/// Resolve an engine's icon. Unknown and missing ids get the neutral magnifier, +/// themed rather than branded. +pub(in crate::features) fn search_engine_icon( + icon: Option<&str>, + palette: ThemePalette, +) -> IconDef { + let key = icon.unwrap_or("default"); + SEARCH_ICONS + .iter() + .find(|(candidate, _, _)| *candidate == key) + .map_or_else( + || IconDef::mono("icons/fe/search.svg", palette.text_muted), + |(_, path, color)| IconDef::mono(path, *color), + ) +} + +#[cfg(test)] +mod tests { + use super::*; + + fn palette() -> ThemePalette { + crate::theme::theme_palette("github-dark") + } + + #[test] + fn every_offered_id_resolves_to_its_own_asset() { + let mut paths: Vec<_> = SEARCH_ENGINE_ICON_IDS + .iter() + .map(|id| search_engine_icon(Some(id), palette()).path) + .collect(); + paths.sort_unstable(); + let total = paths.len(); + paths.dedup(); + assert_eq!(paths.len(), total, "two engines share an icon"); + } + + #[test] + fn unknown_and_missing_ids_get_the_neutral_magnifier() { + let fallback = search_engine_icon(Some("default"), palette()); + assert_eq!(search_engine_icon(None, palette()), fallback); + assert_eq!(search_engine_icon(Some("altavista"), palette()), fallback); + assert_eq!(fallback.path, "icons/fe/search.svg"); + } +} diff --git a/crates/nyaterm-desktop/src/features/inspector/ai_widgets/transcript.rs b/crates/nyaterm-desktop/src/features/inspector/ai_widgets/transcript.rs index 55138183..cb89fc88 100644 --- a/crates/nyaterm-desktop/src/features/inspector/ai_widgets/transcript.rs +++ b/crates/nyaterm-desktop/src/features/inspector/ai_widgets/transcript.rs @@ -79,9 +79,11 @@ impl NyaTermApp { .flex() .items_center() .justify_center() - .text_size(px(22.)) - .text_color(rgb(palette.warning)) - .child("!"), + .child(crate::features::mono_icon( + "icons/warning.svg", + rgb(palette.warning).into(), + 22., + )), ) .child( div() diff --git a/crates/nyaterm-desktop/src/features/inspector/commands.rs b/crates/nyaterm-desktop/src/features/inspector/commands.rs index 44c064d5..b086460d 100644 --- a/crates/nyaterm-desktop/src/features/inspector/commands.rs +++ b/crates/nyaterm-desktop/src/features/inspector/commands.rs @@ -37,12 +37,11 @@ impl NyaTermApp { this.run_history_command(run_index, cx); } })) - .child( - div() - .text_size(px(10.)) - .text_color(rgb(palette.text_dimmed)) - .child("›"), - ) + .child(div().child(crate::features::mono_icon( + "icons/fe/forward.svg", + rgb(palette.text_dimmed).into(), + 10., + ))) .child( div() .min_w_0() diff --git a/crates/nyaterm-desktop/src/features/layout/workspace/surface/menus.rs b/crates/nyaterm-desktop/src/features/layout/workspace/surface/menus.rs index 63195041..facbde92 100644 --- a/crates/nyaterm-desktop/src/features/layout/workspace/surface/menus.rs +++ b/crates/nyaterm-desktop/src/features/layout/workspace/surface/menus.rs @@ -871,6 +871,7 @@ mod tests { description: None, sort_order, icon: None, + icon_auto_detect: None, auth: None, network: None, post_login: None, diff --git a/crates/nyaterm-desktop/src/features/mod.rs b/crates/nyaterm-desktop/src/features/mod.rs index 966a5ea2..0a5e09be 100644 --- a/crates/nyaterm-desktop/src/features/mod.rs +++ b/crates/nyaterm-desktop/src/features/mod.rs @@ -7,6 +7,7 @@ mod commands; mod connection_editor_window; mod connections; mod formatting; +mod icons; mod inspector; mod layout; mod pages; @@ -46,8 +47,8 @@ pub(in crate::features) use app_state::{ SavedConnectionStartOptions, SessionPaneState, }; pub(in crate::features) use commands::{ - QUICK_COMMAND_COLOR_OPTIONS, QUICK_COMMAND_ICON_OPTIONS, QuickCommandFeatureFocus, - QuickCommandFeatureState, quick_command_category_label, quick_command_sort_mode_from_setting, + QUICK_COMMAND_COLOR_OPTIONS, QuickCommandFeatureFocus, QuickCommandFeatureState, + quick_command_category_label, quick_command_sort_mode_from_setting, quick_command_view_mode_from_setting, }; pub(in crate::features) use connection_editor_window::ConnectionEditorWindow; @@ -56,6 +57,11 @@ pub(in crate::features) use connections::{ ConnectionDropTarget, ConnectionEditorToggle, ConnectionFeatureFocus, ConnectionFeatureState, }; pub(in crate::features) use formatting::*; +pub(in crate::features) use icons::{ + CONNECTION_ICON_OPTIONS, DEFAULT_CONNECTION_ICON, IconDef, QUICK_COMMAND_ICON_OPTIONS, + SEARCH_ENGINE_ICON_IDS, file_entry_icon, infer_connection_icon_key_from_remote_system, + quick_command_icon, resolve_connection_icon, search_engine_icon, +}; pub(in crate::features) use panels::{SendCommandFeatureFocus, SendCommandFeatureState}; pub(in crate::features) use quick_command_window::QuickCommandWindow; pub(in crate::features) use remote::{RemoteOpsFeatureFocus, RemoteOpsFeatureState}; diff --git a/crates/nyaterm-desktop/src/features/pages/mod.rs b/crates/nyaterm-desktop/src/features/pages/mod.rs index 4841a19d..2c60c86b 100644 --- a/crates/nyaterm-desktop/src/features/pages/mod.rs +++ b/crates/nyaterm-desktop/src/features/pages/mod.rs @@ -1,7 +1,7 @@ mod connections; mod migration; mod remote; -mod settings; +pub(in crate::features) mod settings; mod transfers; mod tunnels; mod workspace; diff --git a/crates/nyaterm-desktop/src/features/pages/settings/ai/section.rs b/crates/nyaterm-desktop/src/features/pages/settings/ai/section.rs index 148b631f..9c146be3 100644 --- a/crates/nyaterm-desktop/src/features/pages/settings/ai/section.rs +++ b/crates/nyaterm-desktop/src/features/pages/settings/ai/section.rs @@ -233,7 +233,13 @@ impl NyaTermApp { .cursor_pointer() .hover(move |this| this.bg(rgb(hover))) .child(label) - .when(selected, |this| this.child("*")) + .when(selected, |this| { + this.child(crate::features::mono_icon( + "icons/check.svg", + rgb(palette.primary).into(), + 12., + )) + }) .on_click(cx.listener(move |this, _, _, cx| { this.appearance_menu_open = None; this.update_ai_smart_auto_execute_max_risk( diff --git a/crates/nyaterm-desktop/src/features/pages/settings/mod.rs b/crates/nyaterm-desktop/src/features/pages/settings/mod.rs index 709eb210..b1eddee7 100644 --- a/crates/nyaterm-desktop/src/features/pages/settings/mod.rs +++ b/crates/nyaterm-desktop/src/features/pages/settings/mod.rs @@ -817,7 +817,7 @@ pub(super) fn settings_form_row( } /// Compact on/off switch control (Tauri SettingSwitch look). -pub(super) fn settings_switch( +pub(in crate::features::pages) fn settings_switch( palette: ThemePalette, id: impl Into, checked: bool, diff --git a/crates/nyaterm-desktop/src/features/pages/settings/terminal/helpers.rs b/crates/nyaterm-desktop/src/features/pages/settings/terminal/helpers.rs index 34e8829a..836321fb 100644 --- a/crates/nyaterm-desktop/src/features/pages/settings/terminal/helpers.rs +++ b/crates/nyaterm-desktop/src/features/pages/settings/terminal/helpers.rs @@ -1,60 +1,3 @@ -use super::*; - -pub(super) const SEARCH_ENGINE_ICON_IDS: &[&str] = &[ - "google", - "bing", - "duckduckgo", - "github", - "gitlab", - "baidu", - "yahoo", - "youtube", - "bilibili", - "zhihu", - "openai", - "claude", - "gemini", - "default", -]; - -pub(super) fn search_engine_icon_label(icon: Option<&str>) -> String { - match icon.unwrap_or("default") { - "google" => "G".into(), - "bing" => "B".into(), - "duckduckgo" => "D".into(), - "github" => "GH".into(), - "gitlab" => "GL".into(), - "baidu" => "Bd".into(), - "yahoo" => "Y!".into(), - "youtube" => "YT".into(), - "bilibili" => "Bi".into(), - "zhihu" => "Zh".into(), - "openai" => "AI".into(), - "claude" => "Cl".into(), - "gemini" => "Ge".into(), - _ => "?".into(), - } -} - -pub(super) fn search_engine_icon_color(icon: Option<&str>) -> u32 { - match icon.unwrap_or("default") { - "google" => 0x4285f4, - "bing" => 0x008373, - "duckduckgo" => 0xde5833, - "github" => 0x8b949e, - "gitlab" => 0xfc6d26, - "baidu" => 0x2932e1, - "yahoo" => 0x410093, - "youtube" => 0xff0000, - "bilibili" => 0x00a1d6, - "zhihu" => 0x0084ff, - "openai" => 0x10a37f, - "claude" => 0xd97757, - "gemini" => 0x4285f4, - _ => 0x8b949e, - } -} - pub(super) fn parse_keyword_swatch(value: &str) -> Option { let hex = value.trim().strip_prefix('#').unwrap_or(value.trim()); if hex.len() != 6 { diff --git a/crates/nyaterm-desktop/src/features/pages/transfers/entry_row.rs b/crates/nyaterm-desktop/src/features/pages/transfers/entry_row.rs index 11b97122..8d8b7c92 100644 --- a/crates/nyaterm-desktop/src/features/pages/transfers/entry_row.rs +++ b/crates/nyaterm-desktop/src/features/pages/transfers/entry_row.rs @@ -42,7 +42,7 @@ pub(super) fn transfer_browser_parent_entry_row( .px_2() .text_size(px(12.)) .text_color(rgb(palette.text)) - .child(transfer_entry_icon(true, false, false)) + .child(transfer_entry_icon(palette, "..", true, false, false)) .child(".."), ) .child(transfer_browser_text_cell( @@ -190,6 +190,8 @@ pub(super) fn transfer_browser_entry_row( rgb(palette.text) }) .child(transfer_entry_icon( + palette, + &entry.name, is_directory, entry.file_type == SftpFileType::Symlink, is_marked_or_selected, diff --git a/crates/nyaterm-desktop/src/features/panels/connection_import_overlay.rs b/crates/nyaterm-desktop/src/features/panels/connection_import_overlay.rs index 1e1b6327..304749cb 100644 --- a/crates/nyaterm-desktop/src/features/panels/connection_import_overlay.rs +++ b/crates/nyaterm-desktop/src/features/panels/connection_import_overlay.rs @@ -3,7 +3,7 @@ use gpui::{ rgb, rgba, svg, }; -use crate::features::{NyaTermApp, modal_close_icon_button}; +use crate::features::{NyaTermApp, color_icon, modal_close_icon_button, mono_icon}; use crate::models::ConnectionImportSource; use crate::theme::ThemePalette; @@ -116,7 +116,7 @@ impl NyaTermApp { .child(connection_import_source_card( palette, "connection-import-xshell", - "icons/import/xshell.svg", + "color/brand/xshell.png", "Xshell", ".xts", cx.listener(|this, _, _, cx| { @@ -129,7 +129,7 @@ impl NyaTermApp { .child(connection_import_source_card( palette, "connection-import-mobaxterm", - "icons/import/mobaxterm.svg", + "color/brand/mobaxterm.png", "MobaXterm", ".mxtsessions", cx.listener(|this, _, _, cx| { @@ -142,7 +142,7 @@ impl NyaTermApp { .child(connection_import_source_card( palette, "connection-import-windterm", - "icons/import/windterm.svg", + "color/brand/windterm.png", "WindTerm", ".sessions", cx.listener(|this, _, _, cx| { @@ -250,7 +250,12 @@ fn connection_import_source_card( .cursor_pointer() .hover(move |this| this.border_color(rgb(palette.primary)).bg(hover)) .on_click(on_click) - .child(svg().size(px(40.)).path(icon)) + .child(if icon.starts_with("color/") { + // Vendor logos are full-color rasters; they cannot go through svg(). + color_icon(icon, 40.).into_any_element() + } else { + mono_icon(icon, rgb(palette.text).into(), 40.).into_any_element() + }) .child( div() .max_w_full() diff --git a/crates/nyaterm-desktop/src/features/panels/helpers.rs b/crates/nyaterm-desktop/src/features/panels/helpers.rs index 726c91c3..a63fdef8 100644 --- a/crates/nyaterm-desktop/src/features/panels/helpers.rs +++ b/crates/nyaterm-desktop/src/features/panels/helpers.rs @@ -127,23 +127,14 @@ pub(in crate::features::panels) fn quick_command_icon_mark( icon_tag: Option<&str>, color_tag: Option<&str>, ) -> impl IntoElement { - match icon_tag.and_then(|tag| quick_command_icon_def(palette, tag)) { - Some((label, color)) => div() - .size(px(18.)) - .flex_none() - .rounded_sm() - .border_1() - .border_color(color) - .bg(rgb(palette.input)) - .flex() - .items_center() - .justify_center() - .font_family(crate::features::gpui_code_font_family()) - .text_size(px(8.)) - .font_weight(FontWeight(800.)) - .text_color(color) - .child(label) - .into_any_element(), + match icon_tag.and_then(quick_command_icon) { + Some(def) => crate::features::mono_icon( + def.path, + rgb(def.tint(palette).unwrap_or(palette.text)).into(), + 16., + ) + .into_any_element(), + // No icon chosen: the command still gets its color, as a plain dot. None => div() .size(px(9.)) .flex_none() @@ -177,45 +168,6 @@ pub(in crate::features::panels) fn quick_command_color( } } -fn quick_command_icon_def( - palette: crate::theme::ThemePalette, - icon_tag: &str, -) -> Option<(&'static str, gpui::Rgba)> { - match icon_tag.trim().to_ascii_lowercase().as_str() { - "docker" => Some(("DK", rgb(0x2496ed))), - "k8s" => Some(("K8", rgb(0x326ce5))), - "linux" => Some(("LX", rgb(0xfcc624))), - "ubuntu" => Some(("UB", rgb(0xe95420))), - "debian" => Some(("DB", rgb(0xa81d33))), - "centos" => Some(("CE", rgb(0x7f3f98))), - "fedora" => Some(("FE", rgb(0x3c6eb4))), - "apple" => Some(("AP", rgb(0xa2aaad))), - "github" => Some(("GH", rgb(0xe5e7eb))), - "gitlab" => Some(("GL", rgb(0xfc6d26))), - "nginx" => Some(("NX", rgb(0x009639))), - "redis" => Some(("RD", rgb(0xdc382d))), - "postgres" => Some(("PG", rgb(0x4169e1))), - "mysql" => Some(("MY", rgb(0x4479a1))), - "mongodb" => Some(("MO", rgb(0x47a248))), - "python" => Some(("PY", rgb(0x3776ab))), - "js" => Some(("JS", rgb(0xf7df1e))), - "ts" => Some(("TS", rgb(0x3178c6))), - "rust" => Some(("RS", rgb(0xce412b))), - "go" => Some(("GO", rgb(0x00add8))), - "node" => Some(("ND", rgb(0x339933))), - "php" => Some(("PH", rgb(0x777bb4))), - "aws" => Some(("AWS", rgb(0xff9900))), - "gcp" => Some(("GC", rgb(0x4285f4))), - "terminal" => Some((">$", rgb(palette.success))), - "code" => Some(("", rgb(palette.link))), - "server" => Some(("SR", rgb(palette.link))), - "folder" => Some(("FD", rgb(palette.warning))), - "sparkles" => Some(("AI", rgb(palette.link))), - "bolt" => Some(("BT", rgb(palette.warning))), - _ => None, - } -} - pub(in crate::features::panels) fn quick_command_editor_field( palette: crate::theme::ThemePalette, id: &'static str, diff --git a/crates/nyaterm-desktop/src/features/panels/temporary_ssh_link_overlay.rs b/crates/nyaterm-desktop/src/features/panels/temporary_ssh_link_overlay.rs index 9d1b0d29..add9c06f 100644 --- a/crates/nyaterm-desktop/src/features/panels/temporary_ssh_link_overlay.rs +++ b/crates/nyaterm-desktop/src/features/panels/temporary_ssh_link_overlay.rs @@ -69,12 +69,11 @@ impl NyaTermApp { .flex() .items_center() .gap_2() - .child( - div() - .text_size(px(14.)) - .text_color(rgb(palette.link)) - .child("⚡"), - ) + .child(div().child(crate::features::mono_icon( + "icons/conn/flash.svg", + rgb(palette.link).into(), + 14., + ))) .child( div() .text_sm() diff --git a/crates/nyaterm-desktop/src/features/remote/remote_runtime/stats.rs b/crates/nyaterm-desktop/src/features/remote/remote_runtime/stats.rs index faed7faf..cd19c4c9 100644 --- a/crates/nyaterm-desktop/src/features/remote/remote_runtime/stats.rs +++ b/crates/nyaterm-desktop/src/features/remote/remote_runtime/stats.rs @@ -93,6 +93,9 @@ impl NyaTermApp { stats.load.load15 ); self.terminal.view.status = self.remote_ops.stats.status.clone(); + // The snapshot is the only place the remote OS is reported, + // so this is where a connection's icon can be filled in. + self.apply_auto_detected_connection_icon(&event.session_id, &stats.system); self.remote_ops.stats.data = Some(stats); } Err(error) => { diff --git a/crates/nyaterm-desktop/src/features/shell/activity_bar_runtime.rs b/crates/nyaterm-desktop/src/features/shell/activity_bar_runtime.rs index bac960ad..99507ad6 100644 --- a/crates/nyaterm-desktop/src/features/shell/activity_bar_runtime.rs +++ b/crates/nyaterm-desktop/src/features/shell/activity_bar_runtime.rs @@ -12,6 +12,8 @@ pub(in crate::features) struct ActivityBarDragPayload { pub zone: ActivityBarZone, pub index: usize, pub label: String, + /// The dragged entry's own icon, so the preview reads as the thing being moved. + pub icon_path: &'static str, } pub(in crate::features) struct ActivityBarDragPreview { @@ -46,13 +48,11 @@ impl Render for ActivityBarDragPreview { .border_color(rgb(0x334155)) .bg(rgba(0x151b24dd)) .shadow_lg() - .child( - div() - .text_size(px(10.)) - .font_weight(FontWeight(800.)) - .text_color(rgb(0x93c5fd)) - .child("ACT"), - ) + .child(crate::features::mono_icon( + self.payload.icon_path, + rgb(0x93c5fd).into(), + 14., + )) .child( div() .min_w_0() diff --git a/crates/nyaterm-desktop/src/features/shell/tab_mouse.rs b/crates/nyaterm-desktop/src/features/shell/tab_mouse.rs index 73f96caf..3b53747f 100644 --- a/crates/nyaterm-desktop/src/features/shell/tab_mouse.rs +++ b/crates/nyaterm-desktop/src/features/shell/tab_mouse.rs @@ -120,7 +120,11 @@ impl Render for SessionTabTooltip { .text_color(rgb(0x8f98aa)) .hover(|style| style.bg(rgb(0x334155)).text_color(rgb(0xe5edf7))) .cursor_pointer() - .child("⧉") + .child(crate::features::mono_icon( + "icons/copy.svg", + rgb(0x8f98aa).into(), + 11., + )) .on_click(cx.listener(move |_, _, _, cx| { cx.stop_propagation(); cx.write_to_clipboard(ClipboardItem::new_string( diff --git a/crates/nyaterm-desktop/src/features/terminal/terminal_context_menu_runtime/helpers.rs b/crates/nyaterm-desktop/src/features/terminal/terminal_context_menu_runtime/helpers.rs index f7486a5e..29eb490b 100644 --- a/crates/nyaterm-desktop/src/features/terminal/terminal_context_menu_runtime/helpers.rs +++ b/crates/nyaterm-desktop/src/features/terminal/terminal_context_menu_runtime/helpers.rs @@ -20,8 +20,31 @@ pub(super) fn terminal_ctx_item( label: impl Into, shortcut: Option, on_click: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static, +) -> impl IntoElement { + terminal_ctx_item_with_icon(palette, id, label, None, shortcut, on_click) +} + +pub(super) fn terminal_ctx_item_with_icon( + palette: crate::theme::ThemePalette, + id: impl Into, + label: impl Into, + icon: Option, + shortcut: Option, + on_click: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static, ) -> impl IntoElement { let label = label.into(); + let leading = div() + .flex() + .items_center() + .gap_2() + .when_some(icon, |this, def| { + this.child(crate::features::mono_icon( + def.path, + rgb(def.tint(palette).unwrap_or(palette.text)).into(), + 14., + )) + }) + .child(label); let mut row = div() .id(SharedString::from(id.into())) .h(px(28.)) @@ -35,7 +58,7 @@ pub(super) fn terminal_ctx_item( .cursor_pointer() .hover(|this| this.bg(rgb(palette.hover))) .on_click(on_click) - .child(div().child(label)); + .child(leading); if let Some(shortcut) = shortcut { row = row.child( div() @@ -189,23 +212,3 @@ pub(super) fn selection_as_openable_url(selected: &str) -> Option { } None } - -pub(super) fn search_engine_menu_icon_prefix(icon: Option<&str>) -> String { - let label = match icon.unwrap_or("default") { - "google" => "G", - "bing" => "B", - "duckduckgo" => "D", - "github" => "GH", - "gitlab" => "GL", - "baidu" => "Bd", - "yahoo" => "Y!", - "youtube" => "YT", - "bilibili" => "Bi", - "zhihu" => "Zh", - "openai" => "AI", - "claude" => "Cl", - "gemini" => "Ge", - _ => "·", - }; - format!("[{label}] ") -} diff --git a/crates/nyaterm-desktop/src/features/terminal/terminal_context_menu_runtime/menu.rs b/crates/nyaterm-desktop/src/features/terminal/terminal_context_menu_runtime/menu.rs index 62a5ea59..edbdeec9 100644 --- a/crates/nyaterm-desktop/src/features/terminal/terminal_context_menu_runtime/menu.rs +++ b/crates/nyaterm-desktop/src/features/terminal/terminal_context_menu_runtime/menu.rs @@ -415,11 +415,13 @@ impl NyaTermApp { panel = panel.children(search_engines.into_iter().map(|(name, template, icon)| { let query = selected.clone(); - let icon_prefix = search_engine_menu_icon_prefix(icon.as_deref()); - terminal_ctx_item( + let icon_def = + crate::features::search_engine_icon(icon.as_deref(), palette); + terminal_ctx_item_with_icon( palette, format!("term-ctx-search-{name}"), - format!("{icon_prefix}{name}"), + name.clone(), + Some(icon_def), None, cx.listener(move |this, _, _, cx| { this.close_terminal_context_menu(cx); diff --git a/crates/nyaterm-desktop/src/features/view_widgets/icons.rs b/crates/nyaterm-desktop/src/features/view_widgets/icons.rs index 9daa8754..4e0af958 100644 --- a/crates/nyaterm-desktop/src/features/view_widgets/icons.rs +++ b/crates/nyaterm-desktop/src/features/view_widgets/icons.rs @@ -1,33 +1,47 @@ -use super::*; +use gpui::{AnyElement, Hsla, Img, IntoElement, Svg, div, img, prelude::*, px, rgb, svg}; -/// Monochrome activity-bar SVG icon with glyph fallback. +use crate::features::{IconDef, file_entry_icon}; +use crate::theme::ThemePalette; + +/// Paint a monochrome asset from `icons/**`. +/// +/// GPUI rasterizes `svg()` into an alpha mask and keeps only the coverage +/// channel, so whatever paints the file declares are discarded and `color` is the +/// only thing that reaches the screen. That is what makes one glyph usable in +/// seven tints, and it is also why a full-color logo cannot go through here. +pub(in crate::features) fn mono_icon(path: &'static str, color: Hsla, size_px: f32) -> Svg { + debug_assert!( + path.starts_with("icons/"), + "monochrome icons live under icons/: {path}" + ); + svg() + .size(px(size_px)) + .flex_none() + .path(path) + .text_color(color) +} + +/// Paint a full-color asset from `color/**`. +/// +/// Unlike [`mono_icon`] this keeps the rasterized pixels, so the asset **cannot +/// be tinted** — callers must express selection or hover with surrounding chrome +/// instead. Decoding happens off the render thread, so the first frame after a +/// cold start paints nothing; give the icon a reserved size to avoid a reflow. +pub(in crate::features) fn color_icon(path: &'static str, size_px: f32) -> Img { + debug_assert!( + path.starts_with("color/"), + "full-color icons live under color/: {path}" + ); + img(path).size(px(size_px)).flex_none() +} + +/// Activity-bar icon. pub(in crate::features) fn activity_icon( - path: Option<&'static str>, - glyph: &'static str, - color: gpui::Hsla, + path: &'static str, + color: Hsla, size_px: f32, -) -> gpui::AnyElement { - let size = px(size_px); - if let Some(path) = path { - svg() - .size(size) - .flex_none() - .path(path) - .text_color(color) - .into_any_element() - } else { - div() - .size(size) - .flex_none() - .flex() - .items_center() - .justify_center() - .text_size(px(size_px * 0.72)) - .font_weight(FontWeight(700.)) - .text_color(color) - .child(glyph) - .into_any_element() - } +) -> AnyElement { + mono_icon(path, color, size_px).into_any_element() } /// Faded NyaTerm logo used by empty workspace (Tauri EmptyWorkspaceState). @@ -36,61 +50,63 @@ pub(in crate::features) fn nyaterm_logo_mark( size_px: f32, opacity: f32, ) -> impl IntoElement { - let size = px(size_px); div() - .size(size) + .size(px(size_px)) .flex_none() .opacity(opacity) .flex() .items_center() .justify_center() - .child( - svg() - .size(size) - .path("icons/logo.svg") - .text_color(rgb(palette.text_muted)), - ) + .child(mono_icon( + "icons/logo.svg", + rgb(palette.text_muted).into(), + size_px, + )) } -/// Colored connection/OS icon for saved connection rows (Tauri resolveConnectionIcon). -pub(in crate::features) fn connection_type_icon( +/// Paint a resolved [`IconDef`], honoring whichever element the asset needs. +/// +/// `selected` only reaches monochrome icons: a full-color logo cannot be tinted, +/// so selection has to be carried by the row's own background. That matches the +/// pre-GPUI UI, where these were `` tags and the configured color was inert. +pub(in crate::features) fn themed_icon( palette: ThemePalette, - def: ConnectionIconDef, + def: IconDef, selected: bool, size_px: f32, -) -> gpui::AnyElement { - let size = px(size_px); - let color = if selected { - rgb(palette.link) - } else { - rgb(def.color) - }; - svg() - .size(size) - .flex_none() - .path(def.path) - .text_color(color) - .into_any_element() +) -> AnyElement { + match def.tint(palette) { + Some(color) => { + let color = if selected { palette.link } else { color }; + mono_icon(def.path, rgb(color).into(), size_px).into_any_element() + } + None => color_icon(def.path, size_px).into_any_element(), + } } -/// Tauri file explorer entry icon (folder / symlink / file). +/// Connection / OS icon for saved connection rows, tabs and the session header. +pub(in crate::features) fn connection_type_icon( + palette: ThemePalette, + def: IconDef, + selected: bool, + size_px: f32, +) -> AnyElement { + themed_icon(palette, def, selected, size_px) +} + +/// File explorer entry icon, colored by kind and extension. pub(in crate::features) fn transfer_entry_icon( + palette: ThemePalette, + name: &str, is_directory: bool, is_symlink: bool, selected: bool, -) -> gpui::AnyElement { - let (path, color) = if is_symlink { - ("icons/conn/symlink.svg", 0x67e8f9u32) - } else if is_directory { - ("icons/conn/folder.svg", 0xfbbf24u32) +) -> AnyElement { + let def = file_entry_icon(name, is_directory, is_symlink, palette); + let color = if selected { + palette.link } else { - ("icons/conn/file.svg", 0x94a3b8u32) + def.tint(palette).unwrap_or(palette.text_muted) }; - let color = if selected { 0x58a6ffu32 } else { color }; - svg() - .size(px(16.)) - .flex_none() - .path(path) - .text_color(rgb(color)) - .into_any_element() + mono_icon(def.path, rgb(color).into(), 16.).into_any_element() } diff --git a/crates/nyaterm-desktop/src/features/view_widgets/mod.rs b/crates/nyaterm-desktop/src/features/view_widgets/mod.rs index 803367ea..6aea9596 100644 --- a/crates/nyaterm-desktop/src/features/view_widgets/mod.rs +++ b/crates/nyaterm-desktop/src/features/view_widgets/mod.rs @@ -13,7 +13,7 @@ use crate::models::WorkspaceSplitDirection; use crate::widgets::{mode_button, small_button, status_pill}; use super::{ - ConnectionIconDef, InlineMdStyle, MarkdownBlock, ThemePalette, cloud_sync_history_summary, + InlineMdStyle, MarkdownBlock, ThemePalette, cloud_sync_history_summary, cloud_sync_kind_text_color, cloud_sync_status_dot_color, cloud_sync_status_text_color, compact_id, docker_state_color, docker_state_label, format_history_timestamp_ms, format_rate, parse_inline_markdown, parse_markdown_blocks, tunnel_endpoint, tunnel_name, diff --git a/crates/nyaterm-desktop/src/models/navigation.rs b/crates/nyaterm-desktop/src/models/navigation.rs index d0fd920d..06804dcd 100644 --- a/crates/nyaterm-desktop/src/models/navigation.rs +++ b/crates/nyaterm-desktop/src/models/navigation.rs @@ -82,29 +82,9 @@ impl NavItem { } /// Compact monochrome glyph used as text fallback for the activity bar. - pub(crate) fn glyph(self) -> &'static str { - match self { - NavItem::Workspace => "▣", - NavItem::Connections => "⌂", - NavItem::Tunnels => "⇄", - NavItem::Stats => "◔", - NavItem::Processes => "☰", - NavItem::Docker => "🐋", - NavItem::Transfers => "📁", - NavItem::Settings => "⚙", - NavItem::Migration => "⇪", - NavItem::AiAssistant => "✦", - NavItem::ActiveSessions => "◉", - NavItem::CommandHistory => "⌛", - NavItem::SecurityAuth => "⛨", - NavItem::SyncBackupHistory => "☁", - NavItem::Recording => "●", - } - } - /// Bundled SVG path for activity-bar / toolbar icons. - pub(crate) fn icon_path(self) -> Option<&'static str> { - Some(match self { + pub(crate) fn icon_path(self) -> &'static str { + match self { NavItem::Transfers => "icons/files.svg", NavItem::Tunnels => "icons/network.svg", NavItem::SecurityAuth => "icons/auth.svg", @@ -119,8 +99,8 @@ impl NavItem { NavItem::Docker => "icons/docker.svg", NavItem::Recording => "icons/record.svg", NavItem::Migration => "icons/migration.svg", - NavItem::Workspace => return None, - }) + NavItem::Workspace => "icons/workspace.svg", + } } pub(crate) fn is_left_panel(self) -> bool { @@ -292,23 +272,13 @@ impl ActivityBarEntry { } } - pub(crate) fn glyph(self) -> &'static str { - match self { - Self::Panel(item) => item.glyph(), - Self::QuickCommands => "⚡", - Self::CommandSend => "⏎", - Self::Recording => "●", - Self::Lock => "🔒", - } - } - - pub(crate) fn icon_path(self) -> Option<&'static str> { + pub(crate) fn icon_path(self) -> &'static str { match self { Self::Panel(item) => item.icon_path(), - Self::QuickCommands => Some("icons/commands.svg"), - Self::CommandSend => Some("icons/send.svg"), - Self::Recording => Some("icons/record.svg"), - Self::Lock => Some("icons/lock.svg"), + Self::QuickCommands => "icons/commands.svg", + Self::CommandSend => "icons/send.svg", + Self::Recording => "icons/record.svg", + Self::Lock => "icons/lock.svg", } } } diff --git a/crates/nyaterm-desktop/src/models/session_event_bridge.rs b/crates/nyaterm-desktop/src/models/session_event_bridge.rs index 04f07ff2..0063dd8b 100644 --- a/crates/nyaterm-desktop/src/models/session_event_bridge.rs +++ b/crates/nyaterm-desktop/src/models/session_event_bridge.rs @@ -15,6 +15,10 @@ use super::{TerminalFrameOutputSubmission, TerminalFramePipeline}; const SESSION_EVENT_BRIDGE_DRAIN_BATCH: usize = 512; const SESSION_EVENT_BRIDGE_OUTPUT_BUDGET: usize = 128 * 1024; const SESSION_EVENT_BRIDGE_IDLE_SLEEP: Duration = Duration::from_millis(4); +/// Upper bound on one park in the source drain. The queue wakes this thread as +/// soon as a PTY read lands, so the timeout only bounds how long a fully idle +/// bridge waits before re-checking its stop flag. +const SESSION_EVENT_BRIDGE_WAIT_TIMEOUT: Duration = Duration::from_millis(50); const SESSION_EVENT_BRIDGE_BUSY_SLEEP: Duration = Duration::from_millis(1); const SESSION_EVENT_BRIDGE_UI_OUTPUT_LIMIT: usize = 1024 * 1024; const SESSION_EVENT_BRIDGE_UI_OUTPUT_EVENT_LIMIT: usize = 128 * 1024; @@ -475,22 +479,20 @@ fn run_session_event_bridge( thread::sleep(SESSION_EVENT_BRIDGE_BUSY_SLEEP); continue; } - let Ok(drain) = session_manager.drain_events_with_output_budget( + // Park on the queue rather than polling it: a PTY read wakes this + // thread directly, so the first hop of the echo path no longer spends + // an arbitrary slice of the poll interval waiting to notice. + let Ok(drain) = session_manager.drain_events_blocking_with_output_budget( SESSION_EVENT_BRIDGE_DRAIN_BATCH, SESSION_EVENT_BRIDGE_OUTPUT_BUDGET, + SESSION_EVENT_BRIDGE_WAIT_TIMEOUT, ) else { thread::sleep(SESSION_EVENT_BRIDGE_IDLE_SLEEP); continue; }; state.update_source_stats(&drain.stats); if drain.events.is_empty() { - thread::sleep( - if drain.stats.queued_events > 0 || drain.stats.queued_output_bytes > 0 { - SESSION_EVENT_BRIDGE_BUSY_SLEEP - } else { - SESSION_EVENT_BRIDGE_IDLE_SLEEP - }, - ); + // The park above already absorbed the idle wait. continue; } let mut pending_direct_outputs = Vec::new(); diff --git a/crates/nyaterm-ui/src/lib.rs b/crates/nyaterm-ui/src/lib.rs index 1a6a042c..c2d16e6e 100644 --- a/crates/nyaterm-ui/src/lib.rs +++ b/crates/nyaterm-ui/src/lib.rs @@ -5,6 +5,6 @@ mod widgets; pub use theme::{APPEARANCE_THEME_IDS, ThemePalette, appearance_theme_label, theme_palette}; pub use widgets::{ - capability_line, empty_panel, icon_button, mode_button, section_header, session_info_row, - small_button, status_pill, svg_icon_button, + capability_line, empty_panel, mode_button, section_header, session_info_row, small_button, + status_pill, svg_icon_button, }; diff --git a/docs/architecture/gpui-migration-status.md b/docs/architecture/gpui-migration-status.md index ae58c24d..767ad790 100644 --- a/docs/architecture/gpui-migration-status.md +++ b/docs/architecture/gpui-migration-status.md @@ -37,6 +37,26 @@ these as staged extraction candidates, not as formatting-only refactor targets. ## Completed +- Icon assets are no longer hand-drawn approximations. The migration had redrawn + ~113 SVGs by hand in a thin-stroke style, while the pre-GPUI UI was + overwhelmingly Material Design (140 `react-icons/md` imports against 41 lucide, + and those 41 were shadcn internals) — which is why the two builds looked + nothing alike. Icons are now vendored from their upstream sets through + `scripts/icons.manifest` + `scripts/sync-icons.sh` and committed; see + `docs/third-party-icons.md`. +- The monochrome/full-color split is explicit and enforced. `icons/**` goes + through `mono_icon()`/`svg()` as an alpha mask, `color/**` through + `color_icon()`/`img()` as a raster. This is what let the 33 official distro + logos come back: previously 14 distros shared one tinted Tux, Kubernetes wore + the Docker whale, and 14 services shared a tinted server rack, because `svg()` + cannot carry more than one color. +- The icon lookup tables live in `features/icons/` (connection, quick-command, + search-engine, file-kind, alias normalization, remote-system inference), split + from the element construction in `features/view_widgets/icons.rs`. The + two-letter text badges (`"DK"`, `"K8"`, `"UB"`) and the letter search-engine + badges (`"G"`, `"GH"`) are gone, along with the duplicate badge table the + terminal context menu carried. + - Workspace is already on resolver `3` for the Rust 2024 workspace. - `migration-dashboard` exists as an explicit desktop feature. Default desktop features are empty, so release/default builds do not enable the dashboard. diff --git a/docs/third-party-icons.md b/docs/third-party-icons.md new file mode 100644 index 00000000..2298c772 --- /dev/null +++ b/docs/third-party-icons.md @@ -0,0 +1,58 @@ +# Third-party icon assets + +Everything under `crates/nyaterm-app/assets/` is vendored from an upstream icon +set or carried over from the pre-GPUI NyaTerm release. The mapping from each +bundled file to its exact upstream source is `scripts/icons.manifest`; the +pinned versions live at the top of `scripts/sync-icons.sh`, and re-running that +script reproduces the committed tree. + +Assets are committed rather than fetched at build time, so the build stays +offline. `bash scripts/sync-icons.sh --check` verifies the tree still matches the +manifest. + +## Sources + +| Set | Used for | Version | License | +|---|---|---|---| +| [Material Design Icons](https://github.com/marella/material-design-icons) (`@material-design-icons/svg`) | The bulk of the UI chrome — activity bar, menus, file explorer, session and transfer controls, file-kind icons | 0.14.15 | Apache-2.0 | +| [Simple Icons](https://github.com/simple-icons/simple-icons) | Monochrome brand marks (Docker, Kubernetes, Postgres, distro wordmarks, search engines) | 16.27.1 | CC0-1.0 | +| [VS Code Codicons](https://github.com/microsoft/vscode-codicons) | Window title-bar controls only. Material has no thin-stroke equivalent, and the pre-GPUI UI used this same set. | 0.0.46-24 | CC BY 4.0 | +| [Font Awesome Free](https://github.com/FortAwesome/Font-Awesome) | The default server glyph, plus the AWS and Yahoo marks that Simple Icons no longer ships | 7.3.1 | CC BY 4.0 | +| [Remix Icon](https://github.com/Remix-Design/RemixIcon) | The OpenAI mark, likewise absent from Simple Icons | 4.9.1 | Apache-2.0 | +| NyaTerm | `icons/logo.svg`, and the full-color OS/distro logos and importer brand marks under `color/` | — | Project assets, carried over from the Tauri release | + +CC BY 4.0 requires attribution; this file is that attribution. Apache-2.0 +requires the license and notice be carried; both sets are unmodified copies, and +their upstream repositories above hold the canonical texts. + +## Layout, and why the prefix matters + +``` +crates/nyaterm-app/assets/ +├── icons/** monochrome — painted with svg() through mono_icon() +└── color/** full color — painted with img() through color_icon() +``` + +The split is not cosmetic. GPUI's `svg()` rasterizes to an **alpha mask** and +keeps only the coverage channel, so an asset's own paints are discarded and the +color comes entirely from `.text_color()`. That is what lets one server glyph +serve seven theme hues — and it is also why a multi-color distro logo cannot go +through it, which is how the migration ended up with fourteen distros sharing one +tinted Tux. + +`img()` keeps the rasterized pixels, so a logo survives intact, but it cannot be +tinted and it is decoded asynchronously. + +Two consequences are enforced by tests in `crates/nyaterm-app/src/assets.rs`: + +- **No raster payloads under `icons/`.** GPUI builds resvg without the + `raster-images` feature, so an SVG wrapping a base64 PNG renders as nothing at + all — no error, no log line. The importer brand logos arrived in exactly that + shape and are extracted to real `.png` files under `color/brand/`. +- **Full-color assets stay small.** `img()` rasterizes at twice an asset's + *intrinsic* size, not its display size, and the decoded image is never evicted. + The vendored logos are re-declared at 32px (their `viewBox` untouched), which + is the difference between ~0.5 MB and ~50 MB resident for a set of 16px icons. + +`bash scripts/check-icon-references.sh` checks the other direction: that every +path the UI references is bundled, and that the element matches the prefix. diff --git a/scripts/icons.manifest b/scripts/icons.manifest new file mode 100644 index 00000000..d5249c5b --- /dev/null +++ b/scripts/icons.manifest @@ -0,0 +1,245 @@ +# Icon vendoring manifest, consumed by scripts/sync-icons.sh. +# +# Format: +# +# Source specs: +# md:/ @material-design-icons/svg (Apache-2.0) +# si:.svg simple-icons (CC0-1.0) +# fa: