дзеркало
https://github.com/l0ng-ai/openidea.git
синхронізовано 2026-11-15 06:05:39 +02:00
Порівняти коміти
1
Коміти
| Автор | SHA1 | Дата | |
|---|---|---|---|
|
|
4189dc9b83 |
No files matched your search
@@ -136,6 +136,11 @@ buttons can act on what the user is editing.
|
||||
Toolbar buttons: `View::new(body).button(Button::new("refresh", "Refresh").icon("refresh"))`
|
||||
and `.status("12 items")`.
|
||||
|
||||
An item or button `icon` is an IDE icon name, a registered plugin icon, or a small raster image as a `data:` URI
|
||||
(`data:image/png;base64,…`; PNG, JPEG, GIF or WebP, at most 64 KiB), drawn at the icon's size: an avatar or a pipeline
|
||||
status badge. Fetch the image yourself (a `net` grant) and pass it inline; the IDE never loads URLs for an icon. Older
|
||||
hosts show the generic file icon in its place.
|
||||
|
||||
### Events
|
||||
|
||||
`ViewEvent` carries `Button(id)`, `Activate(key)` (double click or Enter), `Select(key)`,
|
||||
|
||||
@@ -126,6 +126,8 @@ register_plugin!(TodoFinder, views);
|
||||
|
||||
工具栏按钮:`View::new(body).button(Button::new("refresh", "Refresh").icon("refresh"))`,状态行:`.status("12 items")`。
|
||||
|
||||
条目和按钮的 `icon` 可以是 IDE 图标名、已注册的插件图标,或者以 `data:` URI 给出的小尺寸位图(`data:image/png;base64,…`,支持 PNG、JPEG、GIF、WebP,最大 64 KiB),按图标尺寸绘制,适合头像或流水线状态徽章。图片由插件自己获取(需要 `net` 授权)后内联传入,IDE 不会为图标加载 URL。较旧的宿主会在该位置显示通用文件图标。
|
||||
|
||||
### 事件
|
||||
|
||||
`ViewEvent` 包括 `Button(id)`、`Activate(key)`(双击或回车)、`Select(key)`、`Expand(key)`、`Collapse(key)`、
|
||||
|
||||
@@ -3,6 +3,11 @@
|
||||
display: block;
|
||||
}
|
||||
|
||||
.image {
|
||||
object-fit: cover;
|
||||
border-radius: var(--radius-small);
|
||||
}
|
||||
|
||||
.mono {
|
||||
color: var(--icon-color, currentColor);
|
||||
}
|
||||
|
||||
@@ -284,3 +284,30 @@ describe("fileIconName", () => {
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe("image icons", () => {
|
||||
const png =
|
||||
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8DwHwAFBQIAX8jx0gAAAABJRU5ErkJggg==";
|
||||
|
||||
it("renders a raster data URI as an image of the icon's size", () => {
|
||||
const { container } = render(<Icon name={png} size={14} />);
|
||||
const img = container.querySelector("img")!;
|
||||
expect(img.getAttribute("src")).toBe(png);
|
||||
expect([img.width, img.height]).toEqual([14, 14]);
|
||||
expect(container.querySelector("svg")).toBeNull();
|
||||
});
|
||||
|
||||
it("falls back to the file icon for other URIs and oversized images", () => {
|
||||
for (const name of [
|
||||
"https://example.com/avatar.png",
|
||||
"data:image/svg+xml;base64,PHN2Zz48L3N2Zz4=",
|
||||
`data:image/png;base64,${"A".repeat(70 * 1024)}`,
|
||||
"data:image/png;base64,not base64!",
|
||||
]) {
|
||||
const { container, unmount } = render(<Icon name={name} />);
|
||||
expect(container.querySelector("img")).toBeNull();
|
||||
expect(container.querySelector("svg")).toBeTruthy();
|
||||
unmount();
|
||||
}
|
||||
});
|
||||
});
|
||||
+32
-2
@@ -816,8 +816,19 @@ const withIds = (p: Prepared, prefix: string) => (p.hasIds ? p.html.replace(/__I
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/** Largest `data:` image accepted as an icon (an avatar or a status badge a plugin fetched). */
|
||||
const MAX_IMAGE_ICON = 64 * 1024;
|
||||
|
||||
/** A small raster image given as a `data:` URI (PNG, JPEG, GIF, WebP), shown in place of an icon. */
|
||||
function isImageIcon(name: string): boolean {
|
||||
return name.length <= MAX_IMAGE_ICON && /^data:image\/(png|jpeg|gif|webp);base64,[A-Za-z0-9+/]+=*$/.test(name);
|
||||
}
|
||||
|
||||
export interface IconProps {
|
||||
/** Icon name; unknown names render the generic file icon. `symbol-<kind>` renders a symbol node icon. */
|
||||
/**
|
||||
* Icon name; unknown names render the generic file icon. `symbol-<kind>` renders a symbol node icon,
|
||||
* and a raster `data:` image URI (see `isImageIcon`) renders that image.
|
||||
*/
|
||||
name: IconName | (string & {});
|
||||
size?: number;
|
||||
className?: string;
|
||||
@@ -826,7 +837,26 @@ export interface IconProps {
|
||||
label?: string;
|
||||
}
|
||||
|
||||
export function Icon({ name, size = 16, className, style, label }: IconProps) {
|
||||
export function Icon(props: IconProps) {
|
||||
return isImageIcon(props.name) ? <ImageIcon {...props} /> : <SvgIcon {...props} />;
|
||||
}
|
||||
|
||||
function ImageIcon({ name, size = 16, className, style, label }: IconProps) {
|
||||
return (
|
||||
<img
|
||||
className={clsx(styles.icon, styles.image, className)}
|
||||
style={style}
|
||||
src={name}
|
||||
width={size}
|
||||
height={size}
|
||||
alt={label ?? ""}
|
||||
aria-hidden={label ? undefined : true}
|
||||
draggable={false}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function SvgIcon({ name, size = 16, className, style, label }: IconProps) {
|
||||
useFileIconRevision();
|
||||
const uid = useId();
|
||||
const path = iconPath(name);
|
||||
|
||||
Reference in new issue
Заблокувати користувача