fix: suppot display icon on helmet (#26)
This commit is contained in:
parent
ddee80e095
commit
fa43205569
|
|
@ -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})`;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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,21 +15,35 @@ function ViewHelmet ({
|
||||||
const setFavicon = async () => {
|
const setFavicon = async () => {
|
||||||
try {
|
try {
|
||||||
let url = '/appflowy.svg';
|
let url = '/appflowy.svg';
|
||||||
|
|
||||||
if (icon && icon.ty === ViewIconType.Emoji && icon.value) {
|
|
||||||
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 response = await fetch(`${baseUrl}${emojiCode}.svg`);
|
|
||||||
const svgText = await response.text();
|
|
||||||
const blob = new Blob([svgText], { type: 'image/svg+xml' });
|
|
||||||
|
|
||||||
url = URL.createObjectURL(blob);
|
|
||||||
}
|
|
||||||
|
|
||||||
const link = document.querySelector('link[rel*=\'icon\']') as HTMLLinkElement || document.createElement('link');
|
const link = document.querySelector('link[rel*=\'icon\']') as HTMLLinkElement || document.createElement('link');
|
||||||
|
|
||||||
link.type = 'image/svg+xml';
|
if (icon && icon.value) {
|
||||||
|
if (icon.ty === ViewIconType.Emoji) {
|
||||||
|
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 response = await fetch(`${baseUrl}${emojiCode}.svg`);
|
||||||
|
const svgText = await response.text();
|
||||||
|
const blob = new Blob([svgText], { type: 'image/svg+xml' });
|
||||||
|
|
||||||
|
url = URL.createObjectURL(blob);
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
|
||||||
|
|
@ -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]);
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,8 @@
|
||||||
|
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';
|
||||||
|
|
||||||
export async function randomEmoji(skin = 0) {
|
export async function randomEmoji (skin = 0) {
|
||||||
const emojiData = await loadEmojiData();
|
const emojiData = await loadEmojiData();
|
||||||
const emojis = (emojiData as EmojiMartData).emojis;
|
const emojis = (emojiData as EmojiMartData).emojis;
|
||||||
const keys = Object.keys(emojis);
|
const keys = Object.keys(emojis);
|
||||||
|
|
@ -10,11 +11,11 @@ export async function randomEmoji(skin = 0) {
|
||||||
return emojis[randomKey].skins[skin].native;
|
return emojis[randomKey].skins[skin].native;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function loadEmojiData() {
|
export async function loadEmojiData () {
|
||||||
return import('@emoji-mart/data/sets/15/native.json');
|
return import('@emoji-mart/data/sets/15/native.json');
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isFlagEmoji(emoji: string) {
|
export function isFlagEmoji (emoji: string) {
|
||||||
return /\uD83C[\uDDE6-\uDDFF]/.test(emoji);
|
return /\uD83C[\uDDE6-\uDDFF]/.test(emoji);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -45,7 +46,7 @@ let icons: Record<ICON_CATEGORY,
|
||||||
keywords: string[];
|
keywords: string[];
|
||||||
}[]> | undefined;
|
}[]> | undefined;
|
||||||
|
|
||||||
export async function loadIcons(): Promise<
|
export async function loadIcons (): Promise<
|
||||||
Record<
|
Record<
|
||||||
ICON_CATEGORY,
|
ICON_CATEGORY,
|
||||||
{
|
{
|
||||||
|
|
@ -66,20 +67,24 @@ 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 '';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function randomIcon() {
|
export async function randomIcon () {
|
||||||
const icons = await loadIcons();
|
const icons = await loadIcons();
|
||||||
const categories = Object.keys(icons);
|
const categories = Object.keys(icons);
|
||||||
const randomCategory = categories[Math.floor(Math.random() * categories.length)] as ICON_CATEGORY;
|
const randomCategory = categories[Math.floor(Math.random() * categories.length)] as ICON_CATEGORY;
|
||||||
|
|
@ -88,7 +93,7 @@ export async function randomIcon() {
|
||||||
return randomIcon;
|
return randomIcon;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getIcon(id: string) {
|
export async function getIcon (id: string) {
|
||||||
const icons = await loadIcons();
|
const icons = await loadIcons();
|
||||||
|
|
||||||
for (const category of Object.keys(icons)) {
|
for (const category of Object.keys(icons)) {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue