From 53e073299665974e2bcc89e2d0896932bfd31177 Mon Sep 17 00:00:00 2001 From: fantasticit Date: Thu, 12 May 2022 11:10:56 +0800 Subject: [PATCH] feat: adapter dark style --- packages/client/src/helpers/color.tsx | 41 +++++++++++++++++++ packages/client/src/hooks/use-theme.tsx | 19 +++++++-- .../tiptap/core/wrappers/callout/index.tsx | 12 +++++- .../core/wrappers/flow/index.module.scss | 7 +++- .../src/tiptap/core/wrappers/flow/index.tsx | 25 +++++++++-- 5 files changed, 94 insertions(+), 10 deletions(-) diff --git a/packages/client/src/helpers/color.tsx b/packages/client/src/helpers/color.tsx index 8bc0f5bd..0f18283c 100644 --- a/packages/client/src/helpers/color.tsx +++ b/packages/client/src/helpers/color.tsx @@ -23,3 +23,44 @@ const colors = [ const total = colors.length; export const getRandomColor = () => colors[~~(Math.random() * total)]; + +/** + * 将颜色转换为带 alpha 通道的 rgba 值 + * @param hexCode + * @param opacity + * @returns + */ +export const convertColorToRGBA = (hexCode: string, opacity = 1) => { + let r = 0; + let g = 0; + let b = 0; + + if (hexCode.startsWith('rgb')) { + const rgb = hexCode + .replace(/\s/g, '') + .match(/rgb\((.*)\)$/)[1] + .split(','); + + r = +rgb[0]; + g = +rgb[1]; + b = +rgb[2]; + } else if (hexCode.startsWith('#')) { + let hex = hexCode.replace('#', ''); + + if (hex.length === 3) { + hex = `${hex[0]}${hex[0]}${hex[1]}${hex[1]}${hex[2]}${hex[2]}`; + } + + r = parseInt(hex.substring(0, 2), 16); + g = parseInt(hex.substring(2, 4), 16); + b = parseInt(hex.substring(4, 6), 16); + } else { + return hexCode; + } + + if (opacity > 1 && opacity <= 100) { + opacity = opacity / 100; + } + + return `rgba(${r},${g},${b},${opacity})`; +}; diff --git a/packages/client/src/hooks/use-theme.tsx b/packages/client/src/hooks/use-theme.tsx index 278eb046..de61894b 100644 --- a/packages/client/src/hooks/use-theme.tsx +++ b/packages/client/src/hooks/use-theme.tsx @@ -1,4 +1,4 @@ -import { useEffect, useState } from 'react'; +import { useCallback, useEffect, useState } from 'react'; export enum Theme { 'dark' = 'dark', @@ -8,10 +8,10 @@ export enum Theme { export const useTheme = () => { const [theme, setTheme] = useState(Theme.light); - const toggle = () => { + const toggle = useCallback(() => { const nextTheme = theme === 'dark' ? Theme.light : Theme.dark; setTheme(nextTheme); - }; + }, [theme]); useEffect(() => { const body = document.body; @@ -40,6 +40,19 @@ export const useTheme = () => { mql.addEventListener('change', matchMode); }, []); + useEffect(() => { + const config = { attributes: true }; + const callback = function () { + setTheme(document.body.getAttribute('theme-mode') as Theme); + }; + const observer = new MutationObserver(callback); + observer.observe(document.body, config); + + return () => { + observer.disconnect(); + }; + }, []); + return { theme, toggle, diff --git a/packages/client/src/tiptap/core/wrappers/callout/index.tsx b/packages/client/src/tiptap/core/wrappers/callout/index.tsx index 19be6bbe..4de2200a 100644 --- a/packages/client/src/tiptap/core/wrappers/callout/index.tsx +++ b/packages/client/src/tiptap/core/wrappers/callout/index.tsx @@ -1,12 +1,20 @@ -import { useCallback } from 'react'; +import { useCallback, useMemo } from 'react'; import { NodeViewWrapper, NodeViewContent } from '@tiptap/react'; import cls from 'classnames'; import { EmojiPicker } from 'components/emoji-picker'; +import { convertColorToRGBA } from 'helpers/color'; +import { Theme, useTheme } from 'hooks/use-theme'; import styles from './index.module.scss'; export const CalloutWrapper = ({ editor, node, updateAttributes }) => { const { isEditable } = editor; const { emoji, textColor, borderColor, backgroundColor } = node.attrs; + const { theme } = useTheme(); + const backgroundColorOpacity = useMemo(() => { + if (!backgroundColor) return backgroundColor; + if (theme === Theme.dark) return convertColorToRGBA(backgroundColor, 0.85); + return backgroundColor; + }, [backgroundColor, theme]); const onSelectEmoji = useCallback( (emoji) => { @@ -21,7 +29,7 @@ export const CalloutWrapper = ({ editor, node, updateAttributes }) => { className={cls(styles.innerWrap, 'render-wrapper')} style={{ borderColor, - backgroundColor, + backgroundColor: backgroundColorOpacity, }} > {isEditable ? ( diff --git a/packages/client/src/tiptap/core/wrappers/flow/index.module.scss b/packages/client/src/tiptap/core/wrappers/flow/index.module.scss index fd004ac0..90dae64a 100644 --- a/packages/client/src/tiptap/core/wrappers/flow/index.module.scss +++ b/packages/client/src/tiptap/core/wrappers/flow/index.module.scss @@ -6,7 +6,6 @@ .renderWrap { display: flex; - background-color: var(--semi-color-bg-1); border: 1px solid var(--node-border-color); border-radius: var(--border-radius); align-items: center; @@ -15,13 +14,17 @@ &::after { background-color: transparent !important; } + + svg { + background-color: transparent !important; + } } .toolbarWrap { position: absolute; right: 20px; bottom: 20px; - z-index: 1000; + z-index: 10; display: flex; padding: 2px 4px; overflow-x: auto; diff --git a/packages/client/src/tiptap/core/wrappers/flow/index.tsx b/packages/client/src/tiptap/core/wrappers/flow/index.tsx index 15e1c959..98f32d38 100644 --- a/packages/client/src/tiptap/core/wrappers/flow/index.tsx +++ b/packages/client/src/tiptap/core/wrappers/flow/index.tsx @@ -2,8 +2,10 @@ import { NodeViewWrapper } from '@tiptap/react'; import cls from 'classnames'; import { Button, Space } from '@douyinfe/semi-ui'; import { IconMindCenter, IconZoomOut, IconZoomIn } from 'components/icons'; -import { useCallback, useEffect, useMemo, useRef } from 'react'; +import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { Resizeable } from 'components/resizeable'; +import { convertColorToRGBA } from 'helpers/color'; +import { Theme, useTheme } from 'hooks/use-theme'; import { getEditorContainerDOMSize } from 'tiptap/prose-utils'; import { Flow } from 'tiptap/core/extensions/flow'; import styles from './index.module.scss'; @@ -16,8 +18,15 @@ export const FlowWrapper = ({ editor, node, updateAttributes }) => { const isActive = editor.isActive(Flow.name); const { width: maxWidth } = getEditorContainerDOMSize(editor); const { data, width, height } = node.attrs; + const { theme } = useTheme(); const $viewer = useRef(null); const $container = useRef(); + const [bgColor, setBgColor] = useState('var(--semi-color-fill-0)'); + const bgColorOpacity = useMemo(() => { + if (!bgColor) return bgColor; + if (theme === Theme.dark) return convertColorToRGBA(bgColor, 0.85); + return bgColor; + }, [bgColor, theme]); const graphData = useMemo(() => { if (!data) return null; @@ -71,6 +80,8 @@ export const FlowWrapper = ({ editor, node, updateAttributes }) => { div.innerHTML = ''; DrawioViewer.createViewerForElement(div, (viewer) => { $viewer.current = viewer; + const background = viewer?.graph?.background; + background && setBgColor(background); }); } }, []); @@ -90,9 +101,17 @@ export const FlowWrapper = ({ editor, node, updateAttributes }) => { return ( -
+
{graphData && ( -
+
)}