Skip to content

Commit cf7d995

Browse files
authored
Merge pull request #93 from estruyf/poc/animations
v1.1.0
2 parents f45afe7 + f667f40 commit cf7d995

File tree

21 files changed

+2117
-103
lines changed

21 files changed

+2117
-103
lines changed

.vscode/settings.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,7 @@
1515
"demoTime.previousEnabled": true,
1616
"demoTime.showClock": true,
1717
"demoTime.timer": 10,
18+
"exportall.config.relExclusion": [
19+
"/src/preview/webcomponents/BaseShapeComponent.ts"
20+
],
1821
}

package-lock.json

Lines changed: 1450 additions & 69 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -602,6 +602,7 @@
602602
"glob": "^10.3.10",
603603
"handlebars": "^4.7.8",
604604
"jsonc-parser": "^3.3.1",
605+
"mermaid": "^11.6.0",
605606
"mlly": "^1.7.4",
606607
"mocha": "^10.2.0",
607608
"npm-run-all": "^4.1.5",
@@ -622,6 +623,7 @@
622623
"ts-loader": "^9.5.1",
623624
"tsup": "^8.0.1",
624625
"typescript": "^5.2.2",
626+
"uuid": "^11.1.0",
625627
"vfile-matter": "^5.0.0",
626628
"vscrui": "^0.2.0-beta.1229608",
627629
"webpack": "^5.97.1",

src/preview/components/Markdown.tsx

Lines changed: 11 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ export interface IMarkdownProps {
1313
filePath?: string;
1414
content?: string;
1515
vsCodeTheme: any;
16+
isDarkTheme: boolean;
1617
webviewUrl: string | null;
1718
updateTheme: (theme: string) => void;
1819
updateLayout: (layout: string) => void;
@@ -23,6 +24,7 @@ export const Markdown: React.FunctionComponent<IMarkdownProps> = ({
2324
filePath,
2425
content,
2526
vsCodeTheme,
27+
isDarkTheme,
2628
webviewUrl,
2729
updateTheme,
2830
updateLayout,
@@ -140,9 +142,9 @@ export const Markdown: React.FunctionComponent<IMarkdownProps> = ({
140142
React.useEffect(() => {
141143
if (content && content !== prevContent) {
142144
// Passing the theme here as it could be that the theme has been updated
143-
setMarkdown(twoColumnFormatting(content), [[rehypePrettyCode, { theme: vsCodeTheme ? vsCodeTheme : {} }]]);
145+
setMarkdown(twoColumnFormatting(content), [[rehypePrettyCode, { theme: vsCodeTheme ? vsCodeTheme : {} }]], isDarkTheme);
144146
}
145-
}, [content, vsCodeTheme]);
147+
}, [content, vsCodeTheme, isDarkTheme]);
146148

147149
if (!isReady) {
148150
return null;
@@ -152,21 +154,17 @@ export const Markdown: React.FunctionComponent<IMarkdownProps> = ({
152154
return null;
153155
}
154156

155-
if (template) {
156-
return (
157-
<>
158-
{customTheme && <link href={customTheme} rel="stylesheet" />}
159-
160-
<div key={filePath} className={`slide__content__custom ${transition || ""}`} dangerouslySetInnerHTML={{ __html: template }} />
161-
</>
162-
);
163-
}
164-
165157
return (
166158
<>
167159
{customTheme && <link href={customTheme} rel="stylesheet" />}
168160

169-
<div key={filePath} className={`slide__content__inner ${transition || ""}`}>{markdown}</div>
161+
{
162+
template ? (
163+
<div key={filePath} className={`slide__content__custom ${transition || ""}`} dangerouslySetInnerHTML={{ __html: template }} />
164+
) : (
165+
<div key={filePath} className={`slide__content__inner ${transition || ""}`}>{markdown}</div>
166+
)
167+
}
170168
</>
171169
);
172170
};

src/preview/components/MarkdownPreview.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ export const MarkdownPreview: React.FunctionComponent<IMarkdownPreviewProps> = (
2727
const slideRef = React.useRef<HTMLDivElement>(null);
2828
const [mousePosition, setMousePosition] = React.useState<{ x: number; y: number } | null>(null);
2929
const { cursorVisible, resetCursorTimeout } = useCursor();
30-
const { vsCodeTheme } = useTheme();
30+
const { vsCodeTheme, isDarkTheme } = useTheme();
3131
const { scale } = useScale(ref, slideRef);
3232

3333
const messageListener = (message: MessageEvent<EventData<any>>) => {
@@ -110,6 +110,7 @@ export const MarkdownPreview: React.FunctionComponent<IMarkdownPreviewProps> = (
110110
filePath={crntFilePath}
111111
content={content}
112112
vsCodeTheme={vsCodeTheme}
113+
isDarkTheme={isDarkTheme}
113114
webviewUrl={webviewUrl}
114115
updateTheme={setTheme}
115116
updateLayout={setLayout}

src/preview/hooks/useRemark.tsx

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ export type UseRemarkOptions = {
2020

2121
export const useRemark = ({
2222
onError = () => { },
23+
// rehypePlugins = [[rehypeMermaid, { strategy: "inline-svg" }]],
2324
rehypePlugins = [],
2425
rehypeReactOptions,
2526
remarkParseOptions,
@@ -31,7 +32,7 @@ export const useRemark = ({
3132
reactContent: ReactElement | null,
3233
metadata: SlideMetadata | null,
3334
}>,
34-
setMarkdown: (source: string, customPlugins?: PluggableList) => void,
35+
setMarkdown: (source: string, customPlugins?: PluggableList, isDark?: boolean) => void,
3536
getMarkdown: (contents: string) => string,
3637
getFrontMatter: (contents: string) => string,
3738
matter: null | SlideMetadata,
@@ -44,27 +45,28 @@ export const useRemark = ({
4445
*
4546
* @param source - The Markdown source string to process.
4647
* @param customPlugins - An optional list of custom plugins to extend the processing pipeline.
48+
* @param isDark - A boolean indicating if the dark mode is enabled.
4749
* @returns An object containing:
4850
* - `reactContent`: The processed React element representation of the Markdown content.
4951
* - `metadata`: Extracted frontmatter metadata from the Markdown file, if available.
5052
*
5153
* @throws Will call the `onError` handler if an error occurs during processing.
5254
*/
53-
const processMarkdown = async (source: string, customPlugins?: PluggableList): Promise<{
55+
const processMarkdown = async (source: string, customPlugins?: PluggableList, isDark?: boolean): Promise<{
5456
reactContent: ReactElement | null,
5557
metadata: SlideMetadata | null,
5658
}> => {
5759
try {
58-
const vfile = await transformMarkdown(source, remarkParseOptions, remarRehypeOptions, remarkPlugins, [...rehypePlugins, ...(customPlugins || [])], rehypeReactOptions);
60+
const vfile = await transformMarkdown(source, remarkParseOptions, remarRehypeOptions, remarkPlugins, [...rehypePlugins, ...(customPlugins || [])], rehypeReactOptions, { isWebComponent: true, isDark });
5961
return vfile;
6062
} catch (err) {
6163
onError(err as Error);
6264
return { reactContent: null, metadata: null };
6365
}
6466
};
6567

66-
const setMarkdownSource = useCallback((source: string, customPlugins?: PluggableList) => {
67-
processMarkdown(source, customPlugins).then(({ reactContent, metadata }) => {
68+
const setMarkdownSource = useCallback((source: string, customPlugins?: PluggableList, isDark?: boolean) => {
69+
processMarkdown(source, customPlugins, isDark).then(({ reactContent, metadata }) => {
6870
setReactContent(reactContent);
6971
setMetadata(metadata);
7072
});

src/preview/hooks/useTheme.tsx

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,10 @@ import { useState, useEffect } from 'react';
33
import { WebViewMessages } from '../../constants';
44
import { usePrevious } from './usePrevious';
55

6-
export default function useTheme(options?: any) {
6+
export default function useTheme() {
77
const [theme, setTheme] = useState('');
88
const prevTheme = usePrevious(theme);
9+
const [isDarkTheme, setIsDarkTheme] = useState(false);
910
const [vsCodeTheme, setVsCodeTheme] = useState('');
1011

1112
const getThemeName = () => {
@@ -17,16 +18,18 @@ export default function useTheme(options?: any) {
1718
});
1819

1920
const getVsCodeTheme = () => {
21+
const themeType = document.body.getAttribute(`data-vscode-theme-kind`);
22+
setIsDarkTheme(themeType?.includes('dark') || false);
2023

2124
messageHandler.request<any | null>(WebViewMessages.toVscode.getTheme, getThemeName()).then((theme) => {
2225
if (theme === null) {
2326
// Check if light or dark theme
24-
const elm = document.body.getAttribute(`data-vscode-theme-kind`);
25-
if (elm === 'vscode-light') {
27+
const themeType = document.body.getAttribute(`data-vscode-theme-kind`);
28+
if (themeType === 'vscode-light') {
2629
setVsCodeTheme("github-light");
27-
} else if (elm === 'vscode-dark') {
30+
} else if (themeType === 'vscode-dark') {
2831
setVsCodeTheme("github-dark");
29-
} else if (elm === 'vscode-high-contrast') {
32+
} else if (themeType === 'vscode-high-contrast') {
3033
setVsCodeTheme("github-dark-high-contrast");
3134
} else {
3235
setVsCodeTheme("github-light-high-contrast");
@@ -53,6 +56,7 @@ export default function useTheme(options?: any) {
5356
}, []);
5457

5558
return {
56-
vsCodeTheme
59+
vsCodeTheme,
60+
isDarkTheme
5761
};
5862
}

src/preview/index.tsx

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,7 @@ import './themes/unnamed.css';
99
import './themes/monomi.css';
1010
import './themes/quantum.css';
1111
import './themes/frost.css';
12-
import './webcomponents/Show';
13-
import './webcomponents/Arrow';
14-
import './webcomponents/Rectangle';
15-
import './webcomponents/Circle';
12+
import './webcomponents';
1613

1714
declare const acquireVsCodeApi: <T = unknown>() => {
1815
getState: () => T;

src/preview/webcomponents/Arrow.tsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -84,8 +84,7 @@ class ArrowComponent extends BaseShapeComponent {
8484
height="540"
8585
style={{
8686
position: 'absolute',
87-
top: 0,
88-
left: 0,
87+
inset: 0,
8988
pointerEvents: 'none'
9089
}}
9190
>
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
import mermaid, { RenderResult } from 'mermaid';
2+
import * as React from 'react';
3+
import { useState, useEffect, useCallback } from 'react';
4+
import { createRoot, Root } from 'react-dom/client';
5+
import { htmlDecode } from '../../utils/htmlDecode';
6+
import { v4 as uuidv4 } from "uuid";
7+
8+
export interface MermaidProps {
9+
graphData?: string;
10+
id: string;
11+
dark: boolean;
12+
}
13+
14+
export const Mermaid: React.FC<MermaidProps> = ({ graphData, id, dark }) => {
15+
const [element, setElement] = useState<HTMLDivElement>();
16+
const [render_result, setRenderResult] = useState<RenderResult>();
17+
18+
const updateDiagramRef = useCallback((elem: HTMLDivElement) => {
19+
if (!elem) {
20+
return;
21+
}
22+
setElement(elem);
23+
}, []);
24+
25+
useEffect(() => {
26+
mermaid.initialize({
27+
startOnLoad: true,
28+
logLevel: 5,
29+
darkMode: dark,
30+
theme: dark ? 'dark' : 'default',
31+
});
32+
}, [dark]);
33+
34+
useEffect(() => {
35+
if (!element) {
36+
return;
37+
}
38+
if (!render_result?.svg) {
39+
return;
40+
}
41+
42+
element.innerHTML = render_result.svg;
43+
render_result.bindFunctions?.(element);
44+
}, [element, render_result]);
45+
46+
useEffect(() => {
47+
if (graphData?.length === 0) {
48+
return;
49+
}
50+
51+
(async () => {
52+
try {
53+
const rr = await mermaid.render(`${id}-svg`, graphData as string);
54+
setRenderResult(rr);
55+
} catch (e: any) {
56+
console.log(`Error rendering mermaid diagram: ${(e as Error).message}`,);
57+
}
58+
})();
59+
}, [graphData]);
60+
61+
return (
62+
<div
63+
ref={updateDiagramRef}
64+
id={id}
65+
/>
66+
);
67+
};
68+
69+
export class MermaidComponent extends HTMLElement {
70+
private root: ShadowRoot | null = null;
71+
private rootElm: Root | null = null;
72+
73+
constructor() {
74+
super();
75+
}
76+
77+
connectedCallback() {
78+
this.root = this.attachShadow({ mode: 'open' });
79+
const mountPoint = document.createElement('div');
80+
this.root.appendChild(mountPoint);
81+
this.rootElm = createRoot(mountPoint);
82+
this.renderComponent();
83+
}
84+
85+
renderComponent() {
86+
if (this.rootElm) {
87+
const id = this.getAttribute('id') || uuidv4();
88+
const code = this.getAttribute('code') || '';
89+
const dark = this.hasAttribute('dark');
90+
91+
this.rootElm.render(
92+
<Mermaid graphData={htmlDecode(code)} id={id} dark={dark} />
93+
);
94+
}
95+
}
96+
97+
attributeChangedCallback() {
98+
this.renderComponent();
99+
}
100+
101+
static get observedAttributes() {
102+
return ['id', 'code', 'dark'];
103+
}
104+
105+
disconnectedCallback() {
106+
if (this.rootElm) {
107+
this.rootElm.unmount();
108+
}
109+
}
110+
}
111+
112+
customElements.define('dt-mermaid', MermaidComponent);

0 commit comments

Comments
 (0)