mirror of
https://github.com/tradingview/lightweight-charts.git
synced 2024-11-25 16:50:59 +08:00
Address review feedback
This commit is contained in:
parent
5022b3c838
commit
550a377dda
10
.eslintrc.js
10
.eslintrc.js
@ -174,6 +174,16 @@ module.exports = {
|
||||
'react/prop-types': 'off',
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ['**/*.js', '**/*.jsx', '**/*.ts', '**/*.tsx'],
|
||||
globals: {
|
||||
CHART_BACKGROUND_COLOR: true,
|
||||
LINE_LINE_COLOR: true,
|
||||
CHART_TEXT_COLOR: true,
|
||||
AREA_TOP_COLOR: true,
|
||||
AREA_BOTTOM_COLOR: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ['**/*.ts', '**/*.tsx'],
|
||||
excludedFiles: ['dist/**'],
|
||||
|
@ -59,7 +59,7 @@
|
||||
"eslint-plugin-mdx": "~1.16.0",
|
||||
"eslint-plugin-prefer-arrow": "~1.2.1",
|
||||
"eslint-plugin-tsdoc": "~0.2.14",
|
||||
"eslint-plugin-unicorn": "~41.0.0",
|
||||
"eslint-plugin-unicorn": "~40.1.0",
|
||||
"eslint-plugin-react": "~7.28.0",
|
||||
"express": "~4.17.2",
|
||||
"glob": "~7.2.0",
|
||||
@ -92,7 +92,7 @@
|
||||
"bundle-dts": "tsc --noEmit --allowJs dts-config.js && dts-bundle-generator --config dts-config.js",
|
||||
"tsc": "ttsc -p tsconfig.prod.json",
|
||||
"tsc-watch": "npm run tsc -- --watch --preserveWatchOutput",
|
||||
"tsc-verify": "tsc -b tsconfig.composite.json",
|
||||
"tsc-verify": "node website/scripts/generate-versions-dts.js && tsc -b tsconfig.composite.json",
|
||||
"lint": "npm-run-all -p lint:**",
|
||||
"lint:eslint": "eslint --format=unix ./",
|
||||
"lint:md": "markdownlint -i \"**/node_modules/**\" -i \"**/website/docs/api/**\" -i \"**/website/versioned_docs/**/api/**\" \"**/*.md\"",
|
||||
|
@ -2,6 +2,7 @@
|
||||
"references": [
|
||||
{ "path": "./src/tsconfig.composite.json" },
|
||||
{ "path": "./tests/tsconfig.composite.json" },
|
||||
{ "path": "./website/tsconfig.json" },
|
||||
],
|
||||
"include": []
|
||||
}
|
||||
|
@ -334,13 +334,6 @@ async function getConfig() {
|
||||
],
|
||||
...versions.map(typedocPluginForVersion),
|
||||
'./plugins/enhanced-codeblock',
|
||||
[
|
||||
'./plugins/generate-versions-json-dts',
|
||||
{
|
||||
versionsJsonPath: path.resolve('./versions.json'),
|
||||
versionsDtsOutputPath: path.resolve('./versions.d.ts'),
|
||||
},
|
||||
],
|
||||
],
|
||||
};
|
||||
|
||||
|
@ -3,7 +3,7 @@
|
||||
"scripts": {
|
||||
"docusaurus": "docusaurus",
|
||||
"start": "cross-env TYPEDOC_WATCH=true docusaurus start",
|
||||
"build": "docusaurus build",
|
||||
"build": "node scripts/generate-versions-dts.js && docusaurus build",
|
||||
"swizzle": "docusaurus swizzle",
|
||||
"deploy": "docusaurus deploy",
|
||||
"clear": "docusaurus clear",
|
||||
|
@ -1,101 +0,0 @@
|
||||
import * as React from 'react';
|
||||
import { useDocsVersion } from '@docusaurus/theme-common';
|
||||
|
||||
import { importLightweightChartsVersion } from './import-lightweight-charts-version';
|
||||
import styles from './styles.module.css';
|
||||
|
||||
function getSrcDocWithScript(script, parentOrigin) {
|
||||
return `
|
||||
<style>
|
||||
html,
|
||||
body,
|
||||
#container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
margin: 0;
|
||||
}
|
||||
</style>
|
||||
<div id="container"></div>
|
||||
<script>
|
||||
window.run = () => {
|
||||
${script}
|
||||
};
|
||||
|
||||
window.parent.postMessage('ready', '${parentOrigin}');
|
||||
window.__READY_TO_RUN = true;
|
||||
</script>
|
||||
`;
|
||||
}
|
||||
|
||||
export const Chart = props => {
|
||||
const { script } = props;
|
||||
const { origin } = window;
|
||||
const { version } = useDocsVersion();
|
||||
const srcDoc = getSrcDocWithScript(script, origin);
|
||||
const ref = React.useRef();
|
||||
|
||||
/**
|
||||
* iOS Safari seems to run scripts within the iframe in a different order
|
||||
* compared to desktop Chrome, Safari, etc.
|
||||
*
|
||||
* On the desktop browsers the React effect will run first, so the 'ready'
|
||||
* event listener is added before the iframe script calls postMessage.
|
||||
*
|
||||
* On iOS Safari the iframe script runs before the React effect, so the
|
||||
* 'ready' event listener hasn't been added yet!
|
||||
*
|
||||
* We use the __READY_TO_RUN flag to handle this case. If __READY_TO_RUN
|
||||
* is true then we know that the inner window's run function can be
|
||||
* called immediately and we don't need to register a event listener.
|
||||
*/
|
||||
React.useEffect(() => {
|
||||
const lightweightChartsImportPromise = importLightweightChartsVersion(version);
|
||||
const injectCreateChartAndRun = contentWindow => {
|
||||
lightweightChartsImportPromise.then(mod => {
|
||||
const createChart = mod.createChart;
|
||||
|
||||
contentWindow.createChart = (container, options) => {
|
||||
const chart = createChart(container, options);
|
||||
const resizeListener = () => {
|
||||
const boundingClientRect = container.getBoundingClientRect();
|
||||
chart.resize(boundingClientRect.width, boundingClientRect.height);
|
||||
};
|
||||
|
||||
contentWindow.addEventListener('resize', resizeListener, true);
|
||||
|
||||
return chart;
|
||||
};
|
||||
|
||||
contentWindow.run();
|
||||
});
|
||||
};
|
||||
|
||||
if (ref.current.contentWindow.__READY_TO_RUN) {
|
||||
injectCreateChartAndRun(ref.current.contentWindow);
|
||||
return;
|
||||
}
|
||||
|
||||
const readyMessageListener = event => {
|
||||
if (event.origin !== origin || event.source !== ref.current.contentWindow) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.data === 'ready') {
|
||||
injectCreateChartAndRun(event.source);
|
||||
window.removeEventListener('message', readyMessageListener, false);
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener('message', readyMessageListener, false);
|
||||
}, [origin, srcDoc]);
|
||||
|
||||
return (
|
||||
<iframe
|
||||
key={script}
|
||||
ref={ref}
|
||||
srcDoc={srcDoc}
|
||||
className={styles.iframe}
|
||||
/>
|
||||
);
|
||||
};
|
102
website/plugins/enhanced-codeblock/theme/CodeBlock/chart.tsx
Normal file
102
website/plugins/enhanced-codeblock/theme/CodeBlock/chart.tsx
Normal file
@ -0,0 +1,102 @@
|
||||
import { useDocsVersion } from '@docusaurus/theme-common';
|
||||
import * as React from 'react';
|
||||
|
||||
import { importLightweightChartsVersion, LightweightChartsApi } from './import-lightweight-charts-version';
|
||||
import styles from './styles.module.css';
|
||||
|
||||
interface ChartProps {
|
||||
script: string;
|
||||
}
|
||||
|
||||
type IFrameWindow = Window & {
|
||||
createChart: undefined | ((container: HTMLElement, options: never) => void);
|
||||
run: undefined | (() => void);
|
||||
};
|
||||
|
||||
function getSrcDocWithScript(script: string, parentOrigin: string): string {
|
||||
return `
|
||||
<style>
|
||||
html,
|
||||
body,
|
||||
#container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
margin: 0;
|
||||
}
|
||||
</style>
|
||||
<div id="container"></div>
|
||||
<script>
|
||||
window.run = () => {
|
||||
${script}
|
||||
};
|
||||
</script>
|
||||
`;
|
||||
}
|
||||
|
||||
export const Chart = (props: ChartProps): JSX.Element => {
|
||||
const { script } = props;
|
||||
const { origin } = window;
|
||||
const { version } = useDocsVersion();
|
||||
const srcDoc = getSrcDocWithScript(script, origin);
|
||||
const ref = React.useRef<HTMLIFrameElement>(null);
|
||||
|
||||
React.useEffect(
|
||||
() => {
|
||||
const iframeElement = ref.current;
|
||||
const iframeWindow = iframeElement?.contentWindow as IFrameWindow;
|
||||
const iframeDocument = iframeElement?.contentDocument;
|
||||
|
||||
if (iframeElement === null || !iframeWindow || !iframeDocument) {
|
||||
return;
|
||||
}
|
||||
|
||||
const injectCreateChartAndRun = () => {
|
||||
importLightweightChartsVersion(version).then((mod: LightweightChartsApi) => {
|
||||
const createChart = mod.createChart;
|
||||
|
||||
iframeWindow.createChart = (container: HTMLElement, options: never) => {
|
||||
const chart = createChart(container, options);
|
||||
const resizeListener = () => {
|
||||
const boundingClientRect = container.getBoundingClientRect();
|
||||
chart.resize(boundingClientRect.width, boundingClientRect.height);
|
||||
};
|
||||
|
||||
iframeWindow.addEventListener('resize', resizeListener, true);
|
||||
|
||||
return chart;
|
||||
};
|
||||
|
||||
if (iframeWindow.run !== undefined) {
|
||||
iframeWindow.run();
|
||||
}
|
||||
})
|
||||
.catch((err: unknown) => {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(err);
|
||||
});
|
||||
};
|
||||
|
||||
if (iframeDocument.readyState === 'complete' && iframeWindow.run !== undefined) {
|
||||
injectCreateChartAndRun();
|
||||
} else {
|
||||
const iframeLoadListener = () => {
|
||||
injectCreateChartAndRun();
|
||||
iframeElement.removeEventListener('load', iframeLoadListener);
|
||||
};
|
||||
|
||||
iframeElement.addEventListener('load', iframeLoadListener);
|
||||
}
|
||||
},
|
||||
[origin, srcDoc]
|
||||
);
|
||||
|
||||
return (
|
||||
<iframe
|
||||
key={script}
|
||||
ref={ref}
|
||||
srcDoc={srcDoc}
|
||||
className={styles.iframe}
|
||||
/>
|
||||
);
|
||||
};
|
@ -1,20 +1,14 @@
|
||||
import type { Version } from '../../../../versions';
|
||||
|
||||
interface VersionAgnosticIChartApi {
|
||||
resize: (width: number, height: number) => void;
|
||||
}
|
||||
export type LightweightChartsApi = typeof import('lightweight-charts-local') | typeof import('lightweight-charts-3.8');
|
||||
|
||||
interface VersionAgnosticLightweightChartsModule {
|
||||
createChart: (container: HTMLElement, options: unknown) => VersionAgnosticIChartApi;
|
||||
}
|
||||
|
||||
export function importLightweightChartsVersion(version: string): Promise<VersionAgnosticLightweightChartsModule> {
|
||||
export function importLightweightChartsVersion(version: string): Promise<LightweightChartsApi> {
|
||||
switch (version as Version | 'current') {
|
||||
case 'current': {
|
||||
return import('lightweight-charts-local') as Promise<VersionAgnosticLightweightChartsModule>;
|
||||
return import('lightweight-charts-local');
|
||||
}
|
||||
case '3.8': {
|
||||
return import('lightweight-charts-3.8') as Promise<VersionAgnosticLightweightChartsModule>;
|
||||
return import('lightweight-charts-3.8');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,22 +0,0 @@
|
||||
const { readFile, writeFile } = require('fs/promises');
|
||||
|
||||
function getDtsContent(versions) {
|
||||
return `declare type Version = ${versions.map(x => `'${x}'`).join(' | ')};
|
||||
declare const versions: Version[];
|
||||
export type { Version };
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
export default versions;
|
||||
`;
|
||||
}
|
||||
|
||||
module.exports = function generateVersionsJsonDts(context, options) {
|
||||
return {
|
||||
name: 'generate-versions-json-dts',
|
||||
async loadContent() {
|
||||
const file = await readFile(options.versionsJsonPath, 'utf-8');
|
||||
const versions = JSON.parse(file);
|
||||
const dtsContent = getDtsContent(versions);
|
||||
await writeFile(options.versionsDtsOutputPath, dtsContent);
|
||||
},
|
||||
};
|
||||
};
|
27
website/scripts/generate-versions-dts.js
Normal file
27
website/scripts/generate-versions-dts.js
Normal file
@ -0,0 +1,27 @@
|
||||
const { readFile, writeFile } = require('fs/promises');
|
||||
const { resolve } = require('path');
|
||||
|
||||
function getDtsContent(versions) {
|
||||
return `declare type Version = ${versions.map(x => `'${x}'`).join(' | ')};
|
||||
declare const versions: Version[];
|
||||
export type { Version };
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
export default versions;
|
||||
`;
|
||||
}
|
||||
|
||||
const versionsJsonPath = resolve(__dirname, '../versions.json');
|
||||
const versionsDtsOutputPath = resolve(__dirname, '../versions.d.ts');
|
||||
|
||||
async function generateVersionsJsonDts() {
|
||||
const file = await readFile(versionsJsonPath, 'utf-8');
|
||||
const versions = JSON.parse(file);
|
||||
const dtsContent = getDtsContent(versions);
|
||||
await writeFile(versionsDtsOutputPath, dtsContent);
|
||||
}
|
||||
|
||||
generateVersionsJsonDts()
|
||||
.catch(err => {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(err);
|
||||
});
|
@ -1,9 +0,0 @@
|
||||
module.exports = {
|
||||
globals: {
|
||||
CHART_BACKGROUND_COLOR: true,
|
||||
LINE_LINE_COLOR: true,
|
||||
CHART_TEXT_COLOR: true,
|
||||
AREA_TOP_COLOR: true,
|
||||
AREA_BOTTOM_COLOR: true,
|
||||
},
|
||||
};
|
@ -30,11 +30,7 @@ export const ChartComponent = props => {
|
||||
});
|
||||
chart.timeScale().fitContent();
|
||||
|
||||
const newSeries = chart.addAreaSeries({
|
||||
lineColor,
|
||||
topColor: areaTopColor,
|
||||
bottomColor: areaBottomColor,
|
||||
});
|
||||
const newSeries = chart.addAreaSeries();
|
||||
newSeries.setData(data);
|
||||
|
||||
window.addEventListener('resize', handleResize);
|
||||
@ -45,7 +41,7 @@ export const ChartComponent = props => {
|
||||
chart.remove();
|
||||
};
|
||||
},
|
||||
[data, backgroundColor, lineColor, textColor]
|
||||
[data, backgroundColor, lineColor, textColor, areaTopColor, areaBottomColor]
|
||||
);
|
||||
|
||||
return (
|
||||
|
@ -16,21 +16,15 @@ export interface ThemedChartProps {
|
||||
}
|
||||
|
||||
function getThemeColors(isDarkTheme: boolean): ThemedChartColors {
|
||||
return isDarkTheme
|
||||
? {
|
||||
backgroundColor: themeColors.DARK.CHART_BACKGROUND_COLOR,
|
||||
lineColor: themeColors.DARK.LINE_LINE_COLOR,
|
||||
textColor: themeColors.DARK.CHART_TEXT_COLOR,
|
||||
areaTopColor: themeColors.DARK.AREA_TOP_COLOR,
|
||||
areaBottomColor: themeColors.DARK.AREA_BOTTOM_COLOR,
|
||||
}
|
||||
: {
|
||||
backgroundColor: themeColors.LIGHT.CHART_BACKGROUND_COLOR,
|
||||
lineColor: themeColors.LIGHT.LINE_LINE_COLOR,
|
||||
textColor: themeColors.LIGHT.CHART_TEXT_COLOR,
|
||||
areaTopColor: themeColors.LIGHT.AREA_TOP_COLOR,
|
||||
areaBottomColor: themeColors.LIGHT.AREA_BOTTOM_COLOR,
|
||||
};
|
||||
const themeKey = isDarkTheme ? 'DARK' : 'LIGHT';
|
||||
|
||||
return {
|
||||
backgroundColor: themeColors[themeKey].CHART_BACKGROUND_COLOR,
|
||||
lineColor: themeColors[themeKey].LINE_LINE_COLOR,
|
||||
textColor: themeColors[themeKey].CHART_TEXT_COLOR,
|
||||
areaTopColor: themeColors[themeKey].AREA_TOP_COLOR,
|
||||
areaBottomColor: themeColors[themeKey].AREA_BOTTOM_COLOR,
|
||||
};
|
||||
}
|
||||
|
||||
export function useThemedChartColors(): ThemedChartColors {
|
||||
|
@ -1,5 +1,5 @@
|
||||
const greenWithAlpha = alpha => `rgba(15, 216, 62, ${alpha})`;
|
||||
const redWithAlpha = alpha => `rgb(255, 64, 64, ${alpha})`;
|
||||
const redWithAlpha = alpha => `rgba(255, 64, 64, ${alpha})`;
|
||||
const blueWithAlpha = alpha => `rgba(41, 98, 255, ${alpha})`;
|
||||
|
||||
export const themeColors = {
|
||||
|
@ -3,7 +3,6 @@
|
||||
"compilerOptions": {
|
||||
"strict": true,
|
||||
"resolveJsonModule": true,
|
||||
"noEmit": true
|
||||
},
|
||||
"include": [
|
||||
"./**/*.ts",
|
||||
|
@ -37,6 +37,11 @@ This will create a web page accessible by default on <http://localhost:1234/>.
|
||||
|
||||
The example _React component_ on this page may not fit your requirements completely. Creating a general purpose declarative wrapper for Lightweight Charts' imperative API is a challenge, but hopefully you can adapt this example to your use case.
|
||||
|
||||
:::info
|
||||
|
||||
For this example we are using props to set chart colors based on the current theme (light or dark). In your real code it might be a better idea to use a [Context](https://reactjs.org/docs/context.html#when-to-use-context).
|
||||
:::
|
||||
|
||||
import { ThemedChart } from '@site/src/components/tutorials/themed-chart-colors-wrapper';
|
||||
import CodeBlock from '@theme/CodeBlock';
|
||||
import code from '!!raw-loader!@site/src/components/tutorials/simple-react-example';
|
||||
|
@ -212,6 +212,11 @@ ChildComponent.displayName = 'ChildComponent';
|
||||
|
||||
By considering all the above you could end up with Chart/Series components looking like the following
|
||||
|
||||
:::info
|
||||
|
||||
For this example we are using props to set chart colors based on the current theme (light or dark). In your real code it might be a better idea to use a [Context](https://reactjs.org/docs/context.html#when-to-use-context).
|
||||
:::
|
||||
|
||||
import { ThemedChart } from '@site/src/components/tutorials/themed-chart-colors-wrapper';
|
||||
import CodeBlock from '@theme/CodeBlock';
|
||||
import code from '!!raw-loader!@site/src/components/tutorials/advanced-react-example';
|
||||
|
Loading…
Reference in New Issue
Block a user