mirror of
https://github.com/ByteAtATime/raycast-linux.git
synced 2025-09-02 04:07:23 +00:00
106 lines
2.9 KiB
TypeScript
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;
|
|
}
|