raycast-linux/src/lib/assets.ts
2025-07-29 17:51:30 +00:00

106 lines
2.9 KiB
TypeScript

import { type ImageLike } from '@flare/protocol';
import { convertFileSrc } from '@tauri-apps/api/core';
import { mode } from 'mode-watcher';
import path from 'path';
import type { ColorLike } from './props';
// this matches any emoji character (u flag = unicode, \p{Emoji} = any unicode emoji)
const EMOJI_REGEX = /\p{Emoji}/u;
const graphemeSegmenter = new Intl.Segmenter();
const iconIsEmoji = (icon: string) => {
// explanation: some emojis are made up of multiple characters, so we need to check if the icon is a single emoji
// first of all, we need to segment the icon into graphemes (characters)
// if it has a single visual character, and one of the corresponding code points is an emoji, then we consider it to be an emoji
// i genuinely hate unicode pls send help
const graphemes = graphemeSegmenter.segment(icon);
return Array.from(graphemes).length === 1 && EMOJI_REGEX.test(icon);
};
type ImageMask = 'circle' | 'roundedRectangle';
export type ResolvedIcon =
| { type: 'raycast'; name: string; tintColor?: ColorLike }
| {
type: 'image';
src: string;
mask?: ImageMask;
tintColor?: ColorLike;
}
| { type: 'emoji'; emoji: string };
export function resolveIcon(
icon: ImageLike | undefined | null,
assetsBasePath: string
): ResolvedIcon | null {
if (!icon) return null;
if (typeof icon === 'string') {
if (iconIsEmoji(icon)) {
return { type: 'emoji', emoji: icon };
}
// TODO: better heuristic?
if (icon.endsWith('-16')) {
return { type: 'raycast', name: icon };
}
if (icon.startsWith('http') || icon.startsWith('data:') || icon.startsWith('blob:')) {
return { type: 'image', src: icon };
}
if (icon.startsWith('/')) {
return { type: 'image', src: convertFileSrc(icon) };
}
return { type: 'image', src: convertFileSrc(path.join(assetsBasePath, icon)) };
}
if (typeof icon === 'object' && 'source' in icon) {
const source =
typeof icon.source === 'object'
? mode.current === 'dark'
? icon.source.dark
: icon.source.light
: icon.source;
// TODO: better heuristic?
if (source.endsWith('-16')) {
return {
type: 'raycast',
name: source,
tintColor:
// TODO: actually handle adjustContrast
typeof icon.tintColor === 'object'
? { ...icon.tintColor, adjustContrast: false }
: icon.tintColor
};
}
let src: string;
if (source.startsWith('http') || source.startsWith('data:') || source.startsWith('blob:')) {
src = source;
} else if (source.startsWith('/')) {
src = convertFileSrc(source);
} else {
src = convertFileSrc(path.join(assetsBasePath, source));
}
return {
type: 'image',
src: src,
mask: icon.mask,
tintColor:
typeof icon.tintColor === 'object' && icon.tintColor
? { adjustContrast: false, ...icon.tintColor }
: icon.tintColor
};
}
if (typeof icon === 'object' && 'fileIcon' in icon) {
return { type: 'image', src: convertFileSrc(icon.fileIcon) };
}
return null;
}