Address review feedback

This commit is contained in:
Edward Dewhurst 2022-04-06 17:35:55 +01:00
parent 5022b3c838
commit 550a377dda
17 changed files with 169 additions and 175 deletions

View File

@ -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/**'],

View File

@ -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\"",

View File

@ -2,6 +2,7 @@
"references": [
{ "path": "./src/tsconfig.composite.json" },
{ "path": "./tests/tsconfig.composite.json" },
{ "path": "./website/tsconfig.json" },
],
"include": []
}

View File

@ -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'),
},
],
],
};

View File

@ -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",

View File

@ -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}
/>
);
};

View 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}
/>
);
};

View File

@ -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');
}
}
}

View File

@ -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);
},
};
};

View 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);
});

View File

@ -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,
},
};

View File

@ -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 (

View File

@ -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 {

View File

@ -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 = {

View File

@ -3,7 +3,6 @@
"compilerOptions": {
"strict": true,
"resolveJsonModule": true,
"noEmit": true
},
"include": [
"./**/*.ts",

View File

@ -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';

View File

@ -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';