fix: suppot display icon on helmet (#26)

This commit is contained in:
Kilu.He 2025-01-14 15:08:16 +08:00 committed by GitHub
parent ddee80e095
commit fa43205569
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 97 additions and 32 deletions

View File

@ -47,7 +47,7 @@ const fetchMetaData = async (namespace: string, publishName?: string) => {
let url = `${baseURL}/api/workspace/published/${namespace}`; let url = `${baseURL}/api/workspace/published/${namespace}`;
if (publishName) { if (publishName) {
url += `/${publishName}`; url = `${baseURL}/api/workspace/v1/published/${namespace}/${publishName}`;
} }
logger.info(`Fetching meta data from ${url}`); logger.info(`Fetching meta data from ${url}`);
@ -60,7 +60,11 @@ const fetchMetaData = async (namespace: string, publishName?: string) => {
throw new Error(`HTTP error! Status: ${response.status}`); throw new Error(`HTTP error! Status: ${response.status}`);
} }
return response.json(); const data = await response.json();
logger.info(`Fetched meta data from ${url}: ${JSON.stringify(data)}`);
return data;
} catch (error) { } catch (error) {
logger.error(`Error fetching meta data ${error}`); logger.error(`Error fetching meta data ${error}`);
return null; return null;
@ -130,7 +134,11 @@ const createServer = async (req: Request) => {
const data = await fetchMetaData(namespace, publishName); const data = await fetchMetaData(namespace, publishName);
if (publishName) { if (publishName) {
metaData = data; if (data.code === 0) {
metaData = data.data;
} else {
logger.error(`Error fetching meta data: ${JSON.stringify(data)}`);
}
} else { } else {
const publishInfo = data?.data?.info; const publishInfo = data?.data?.info;
@ -165,6 +173,7 @@ const createServer = async (req: Request) => {
if (metaData && metaData.view) { if (metaData && metaData.view) {
const view = metaData.view; const view = metaData.view;
const emoji = view.icon?.ty === 0 && view.icon?.value; const emoji = view.icon?.ty === 0 && view.icon?.value;
const icon = view.icon?.ty === 2 && view.icon?.value;
const titleList = []; const titleList = [];
if (emoji) { if (emoji) {
@ -172,6 +181,15 @@ const createServer = async (req: Request) => {
const baseUrl = 'https://raw.githubusercontent.com/googlefonts/noto-emoji/main/svg/emoji_u'; const baseUrl = 'https://raw.githubusercontent.com/googlefonts/noto-emoji/main/svg/emoji_u';
favicon = `${baseUrl}${emojiCode}.svg`; favicon = `${baseUrl}${emojiCode}.svg`;
} else if (icon) {
try {
const { iconContent, color } = JSON.parse(view.icon?.value);
favicon = getIconBase64(iconContent, color);
$('link[rel="icon"]').attr('type', 'image/svg+xml');
} catch (_) {
// Do nothing
}
} }
if (view.name) { if (view.name) {
@ -253,3 +271,30 @@ const start = () => {
start(); start();
export {}; export {};
function getIconBase64 (svgText: string, color: string) {
let newSvgText = svgText.replace(/fill="[^"]*"/g, ``);
newSvgText = newSvgText.replace('<svg', `<svg fill="${argbToRgba(color)}"`);
const base64String = btoa(newSvgText);
return `data:image/svg+xml;base64,${base64String}`;
}
function argbToRgba (color: string): string {
const hex = color.replace(/^#|0x/, '');
const hasAlpha = hex.length === 8;
if (!hasAlpha) {
return color.replace('0x', '#');
}
const r = parseInt(hex.slice(2, 4), 16);
const g = parseInt(hex.slice(4, 6), 16);
const b = parseInt(hex.slice(6, 8), 16);
const a = hasAlpha ? parseInt(hex.slice(0, 2), 16) / 255 : 1;
return `rgba(${r}, ${g}, ${b}, ${a})`;
}

View File

@ -1,4 +1,5 @@
import { ViewIcon, ViewIconType } from '@/application/types'; import { ViewIcon, ViewIconType } from '@/application/types';
import { getIconBase64 } from '@/utils/emoji';
import React, { useEffect } from 'react'; import React, { useEffect } from 'react';
import { Helmet } from 'react-helmet'; import { Helmet } from 'react-helmet';
@ -14,8 +15,10 @@ function ViewHelmet ({
const setFavicon = async () => { const setFavicon = async () => {
try { try {
let url = '/appflowy.svg'; let url = '/appflowy.svg';
const link = document.querySelector('link[rel*=\'icon\']') as HTMLLinkElement || document.createElement('link');
if (icon && icon.ty === ViewIconType.Emoji && icon.value) { if (icon && icon.value) {
if (icon.ty === ViewIconType.Emoji) {
const emojiCode = icon?.value?.codePointAt(0)?.toString(16); // Convert emoji to hex code const emojiCode = icon?.value?.codePointAt(0)?.toString(16); // Convert emoji to hex code
const baseUrl = 'https://raw.githubusercontent.com/googlefonts/noto-emoji/main/svg/emoji_u'; const baseUrl = 'https://raw.githubusercontent.com/googlefonts/noto-emoji/main/svg/emoji_u';
@ -24,11 +27,23 @@ function ViewHelmet ({
const blob = new Blob([svgText], { type: 'image/svg+xml' }); const blob = new Blob([svgText], { type: 'image/svg+xml' });
url = URL.createObjectURL(blob); url = URL.createObjectURL(blob);
}
const link = document.querySelector('link[rel*=\'icon\']') as HTMLLinkElement || document.createElement('link');
link.type = 'image/svg+xml'; link.type = 'image/svg+xml';
} else if (icon.ty === ViewIconType.Icon) {
const {
groupName,
iconName,
color,
} = JSON.parse(icon.value);
const id = `${groupName}/${iconName}`;
url = (await getIconBase64(id, color)) || '';
link.type = 'image/svg+xml';
}
}
link.rel = 'icon'; link.rel = 'icon';
link.href = url; link.href = url;
document.getElementsByTagName('head')[0].appendChild(link); document.getElementsByTagName('head')[0].appendChild(link);

View File

@ -3,7 +3,7 @@ import { FieldType } from '@/application/database-yjs/database.type';
import { Column, useFieldSelector } from '@/application/database-yjs/selector'; import { Column, useFieldSelector } from '@/application/database-yjs/selector';
import { FieldTypeIcon } from '@/components/database/components/field'; import { FieldTypeIcon } from '@/components/database/components/field';
import { ThemeModeContext } from '@/components/main/useAppThemeMode'; import { ThemeModeContext } from '@/components/main/useAppThemeMode';
import { getIconSvgEncodedContent } from '@/utils/emoji'; import { getIconBase64 } from '@/utils/emoji';
import { Tooltip } from '@mui/material'; import { Tooltip } from '@mui/material';
import React, { useContext, useEffect, useMemo, useState } from 'react'; import React, { useContext, useEffect, useMemo, useState } from 'react';
import { ReactComponent as AIIndicatorSvg } from '@/assets/ai_indicator.svg'; import { ReactComponent as AIIndicatorSvg } from '@/assets/ai_indicator.svg';
@ -25,8 +25,8 @@ export function GridColumn ({ column, index }: { column: Column; index: number }
useEffect(() => { useEffect(() => {
if (icon) { if (icon) {
void getIconSvgEncodedContent(icon, isDark ? 'white' : 'black').then((res) => { void getIconBase64(icon, isDark ? 'white' : 'black').then((res) => {
setIconEncodeContent(res); if (res) setIconEncodeContent(res);
}); });
} }
}, [icon, isDark]); }, [icon, isDark]);

View File

@ -1,3 +1,4 @@
import { renderColor } from '@/utils/color';
import { EmojiMartData } from '@emoji-mart/data'; import { EmojiMartData } from '@emoji-mart/data';
import axios from 'axios'; import axios from 'axios';
@ -66,16 +67,20 @@ export async function loadIcons(): Promise<
}); });
} }
export async function getIconSvgEncodedContent(id: string, color: string) { export async function getIconBase64 (id: string, color: string) {
try { try {
const { data } = await axios.get(`/af_icons/${id}.svg`); const response = await fetch(`/af_icons/${id}.svg`);
let svgText = await response.text();
const urlEncodedContent = encodeURIComponent(data.replaceAll('black', color)); svgText = svgText.replace(/fill="[^"]*"/g, ``);
svgText = svgText.replace('<svg', `<svg fill="${renderColor(color)}"`);
return `data:image/svg+xml;utf8,${urlEncodedContent}`; const base64String = btoa(svgText);
} catch (e) {
console.error(e); return `data:image/svg+xml;base64,${base64String}`;
return null; } catch (error) {
console.error('Error setting favicon:', error);
return '';
} }
} }