mirror of https://github.com/fantasticit/think.git
feat: adapter dark style
parent
d26c9a742e
commit
53e0732996
|
@ -23,3 +23,44 @@ const colors = [
|
||||||
|
|
||||||
const total = colors.length;
|
const total = colors.length;
|
||||||
export const getRandomColor = () => colors[~~(Math.random() * total)];
|
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})`;
|
||||||
|
};
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { useEffect, useState } from 'react';
|
import { useCallback, useEffect, useState } from 'react';
|
||||||
|
|
||||||
export enum Theme {
|
export enum Theme {
|
||||||
'dark' = 'dark',
|
'dark' = 'dark',
|
||||||
|
@ -8,10 +8,10 @@ export enum Theme {
|
||||||
export const useTheme = () => {
|
export const useTheme = () => {
|
||||||
const [theme, setTheme] = useState(Theme.light);
|
const [theme, setTheme] = useState(Theme.light);
|
||||||
|
|
||||||
const toggle = () => {
|
const toggle = useCallback(() => {
|
||||||
const nextTheme = theme === 'dark' ? Theme.light : Theme.dark;
|
const nextTheme = theme === 'dark' ? Theme.light : Theme.dark;
|
||||||
setTheme(nextTheme);
|
setTheme(nextTheme);
|
||||||
};
|
}, [theme]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const body = document.body;
|
const body = document.body;
|
||||||
|
@ -40,6 +40,19 @@ export const useTheme = () => {
|
||||||
mql.addEventListener('change', matchMode);
|
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 {
|
return {
|
||||||
theme,
|
theme,
|
||||||
toggle,
|
toggle,
|
||||||
|
|
|
@ -1,12 +1,20 @@
|
||||||
import { useCallback } from 'react';
|
import { useCallback, useMemo } from 'react';
|
||||||
import { NodeViewWrapper, NodeViewContent } from '@tiptap/react';
|
import { NodeViewWrapper, NodeViewContent } from '@tiptap/react';
|
||||||
import cls from 'classnames';
|
import cls from 'classnames';
|
||||||
import { EmojiPicker } from 'components/emoji-picker';
|
import { EmojiPicker } from 'components/emoji-picker';
|
||||||
|
import { convertColorToRGBA } from 'helpers/color';
|
||||||
|
import { Theme, useTheme } from 'hooks/use-theme';
|
||||||
import styles from './index.module.scss';
|
import styles from './index.module.scss';
|
||||||
|
|
||||||
export const CalloutWrapper = ({ editor, node, updateAttributes }) => {
|
export const CalloutWrapper = ({ editor, node, updateAttributes }) => {
|
||||||
const { isEditable } = editor;
|
const { isEditable } = editor;
|
||||||
const { emoji, textColor, borderColor, backgroundColor } = node.attrs;
|
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(
|
const onSelectEmoji = useCallback(
|
||||||
(emoji) => {
|
(emoji) => {
|
||||||
|
@ -21,7 +29,7 @@ export const CalloutWrapper = ({ editor, node, updateAttributes }) => {
|
||||||
className={cls(styles.innerWrap, 'render-wrapper')}
|
className={cls(styles.innerWrap, 'render-wrapper')}
|
||||||
style={{
|
style={{
|
||||||
borderColor,
|
borderColor,
|
||||||
backgroundColor,
|
backgroundColor: backgroundColorOpacity,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{isEditable ? (
|
{isEditable ? (
|
||||||
|
|
|
@ -6,7 +6,6 @@
|
||||||
|
|
||||||
.renderWrap {
|
.renderWrap {
|
||||||
display: flex;
|
display: flex;
|
||||||
background-color: var(--semi-color-bg-1);
|
|
||||||
border: 1px solid var(--node-border-color);
|
border: 1px solid var(--node-border-color);
|
||||||
border-radius: var(--border-radius);
|
border-radius: var(--border-radius);
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
@ -15,13 +14,17 @@
|
||||||
&::after {
|
&::after {
|
||||||
background-color: transparent !important;
|
background-color: transparent !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
svg {
|
||||||
|
background-color: transparent !important;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.toolbarWrap {
|
.toolbarWrap {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: 20px;
|
right: 20px;
|
||||||
bottom: 20px;
|
bottom: 20px;
|
||||||
z-index: 1000;
|
z-index: 10;
|
||||||
display: flex;
|
display: flex;
|
||||||
padding: 2px 4px;
|
padding: 2px 4px;
|
||||||
overflow-x: auto;
|
overflow-x: auto;
|
||||||
|
|
|
@ -2,8 +2,10 @@ import { NodeViewWrapper } from '@tiptap/react';
|
||||||
import cls from 'classnames';
|
import cls from 'classnames';
|
||||||
import { Button, Space } from '@douyinfe/semi-ui';
|
import { Button, Space } from '@douyinfe/semi-ui';
|
||||||
import { IconMindCenter, IconZoomOut, IconZoomIn } from 'components/icons';
|
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 { Resizeable } from 'components/resizeable';
|
||||||
|
import { convertColorToRGBA } from 'helpers/color';
|
||||||
|
import { Theme, useTheme } from 'hooks/use-theme';
|
||||||
import { getEditorContainerDOMSize } from 'tiptap/prose-utils';
|
import { getEditorContainerDOMSize } from 'tiptap/prose-utils';
|
||||||
import { Flow } from 'tiptap/core/extensions/flow';
|
import { Flow } from 'tiptap/core/extensions/flow';
|
||||||
import styles from './index.module.scss';
|
import styles from './index.module.scss';
|
||||||
|
@ -16,8 +18,15 @@ export const FlowWrapper = ({ editor, node, updateAttributes }) => {
|
||||||
const isActive = editor.isActive(Flow.name);
|
const isActive = editor.isActive(Flow.name);
|
||||||
const { width: maxWidth } = getEditorContainerDOMSize(editor);
|
const { width: maxWidth } = getEditorContainerDOMSize(editor);
|
||||||
const { data, width, height } = node.attrs;
|
const { data, width, height } = node.attrs;
|
||||||
|
const { theme } = useTheme();
|
||||||
const $viewer = useRef(null);
|
const $viewer = useRef(null);
|
||||||
const $container = useRef<HTMLElement>();
|
const $container = useRef<HTMLElement>();
|
||||||
|
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(() => {
|
const graphData = useMemo(() => {
|
||||||
if (!data) return null;
|
if (!data) return null;
|
||||||
|
@ -71,6 +80,8 @@ export const FlowWrapper = ({ editor, node, updateAttributes }) => {
|
||||||
div.innerHTML = '';
|
div.innerHTML = '';
|
||||||
DrawioViewer.createViewerForElement(div, (viewer) => {
|
DrawioViewer.createViewerForElement(div, (viewer) => {
|
||||||
$viewer.current = viewer;
|
$viewer.current = viewer;
|
||||||
|
const background = viewer?.graph?.background;
|
||||||
|
background && setBgColor(background);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
@ -90,9 +101,17 @@ export const FlowWrapper = ({ editor, node, updateAttributes }) => {
|
||||||
return (
|
return (
|
||||||
<NodeViewWrapper className={cls(styles.wrap, isActive && styles.isActive)}>
|
<NodeViewWrapper className={cls(styles.wrap, isActive && styles.isActive)}>
|
||||||
<Resizeable isEditable={isEditable} width={width} height={height} maxWidth={maxWidth} onChangeEnd={onResize}>
|
<Resizeable isEditable={isEditable} width={width} height={height} maxWidth={maxWidth} onChangeEnd={onResize}>
|
||||||
<div className={cls(styles.renderWrap, 'render-wrapper')} style={INHERIT_SIZE_STYLE}>
|
<div
|
||||||
|
className={cls(styles.renderWrap, 'render-wrapper')}
|
||||||
|
style={{ ...INHERIT_SIZE_STYLE, backgroundColor: bgColorOpacity }}
|
||||||
|
>
|
||||||
{graphData && (
|
{graphData && (
|
||||||
<div className="mxgraph" style={{ width, height }} ref={setMxgraph} data-mxgraph={graphData}></div>
|
<div
|
||||||
|
className="mxgraph"
|
||||||
|
style={{ width: maxWidth, height }}
|
||||||
|
ref={setMxgraph}
|
||||||
|
data-mxgraph={graphData}
|
||||||
|
></div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue