Skip to content

docs(website): optimize playground config editor #4242

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Dec 9, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/website/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"build": "docusaurus build",
"clear": "docusaurus clear",
"format": "prettier --write \"./**/*.{md,mdx,ts,js,tsx,jsx}\" --ignore-path ../../.prettierignore",
"lint": "eslint . --ext .js,.ts --ignore-path ../../.eslintignore",
"serve": "docusaurus serve",
"start": "docusaurus start",
"swizzle": "docusaurus swizzle"
Expand Down
35 changes: 24 additions & 11 deletions packages/website/src/components/OptionsSelector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,19 +43,32 @@ function OptionsSelector({
const [copyLink, setCopyLink] = useState<boolean>(false);
const [copyMarkdown, setCopyMarkdown] = useState<boolean>(false);

const updateTS = useCallback((version: string) => {
setState({ ts: version });
}, []);
const updateTS = useCallback(
(version: string) => {
setState({ ts: version });
},
[setState],
);

const updateRules = useCallback((rules: RulesRecord) => {
setState({ rules: rules });
setEslintModal(false);
}, []);
const updateRules = useCallback(
(rules?: RulesRecord) => {
if (rules) {
setState({ rules: rules });
}
setEslintModal(false);
},
[setState],
);

const updateTsConfig = useCallback((config: CompilerFlags) => {
setState({ tsConfig: config });
setTypeScriptModal(false);
}, []);
const updateTsConfig = useCallback(
(config?: CompilerFlags) => {
if (config) {
setState({ tsConfig: config });
}
setTypeScriptModal(false);
},
[setState],
);

const copyLinkToClipboard = useCallback(async () => {
await navigator.clipboard.writeText(document.location.toString());
Expand Down
30 changes: 17 additions & 13 deletions packages/website/src/components/config/ConfigEslint.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@ import React, { useCallback, useEffect, useState } from 'react';
import type { RulesRecord, RuleEntry } from '@typescript-eslint/website-eslint';

import ConfigEditor, { ConfigOptionsType } from './ConfigEditor';
import { RuleDetails } from '../types';
import type { RuleDetails } from '../types';
import { shallowEqual } from '../lib/shallowEqual';

export interface ModalEslintProps {
readonly isOpen: boolean;
readonly onClose: (rules: RulesRecord) => void;
readonly onClose: (value?: RulesRecord) => void;
readonly ruleOptions: RuleDetails[];
readonly rules: RulesRecord;
}
Expand Down Expand Up @@ -55,19 +56,22 @@ function ConfigEslint(props: ModalEslintProps): JSX.Element {

const onClose = useCallback(
(newConfig: Record<string, unknown>) => {
props.onClose(
Object.fromEntries(
Object.entries(newConfig)
.map<[string, unknown]>(([name, value]) =>
Array.isArray(value) && value.length === 1
? [name, value[0]]
: [name, value],
)
.filter(checkOptions),
),
const cfg = Object.fromEntries(
Object.entries(newConfig)
.map<[string, unknown]>(([name, value]) =>
Array.isArray(value) && value.length === 1
? [name, value[0]]
: [name, value],
)
.filter(checkOptions),
);
if (!shallowEqual(cfg, props.rules)) {
props.onClose(cfg);
} else {
props.onClose();
}
},
[props.onClose],
[props.onClose, props.rules],
);

return (
Expand Down
20 changes: 13 additions & 7 deletions packages/website/src/components/config/ConfigTypeScript.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import React, { useCallback } from 'react';
import tsConfigOptions from '../tsConfigOptions.json';

import type { CompilerFlags } from '../types';
import ConfigEditor from './ConfigEditor';
import type { CompilerFlags } from '../types';
import { shallowEqual } from '../lib/shallowEqual';

interface ModalTypeScriptProps {
isOpen: boolean;
onClose: (config: CompilerFlags) => void;
config?: CompilerFlags;
readonly isOpen: boolean;
readonly onClose: (config?: CompilerFlags) => void;
readonly config?: CompilerFlags;
}

function checkOptions(item: [string, unknown]): item is [string, boolean] {
Expand All @@ -17,11 +18,16 @@ function checkOptions(item: [string, unknown]): item is [string, boolean] {
function ConfigTypeScript(props: ModalTypeScriptProps): JSX.Element {
const onClose = useCallback(
(newConfig: Record<string, unknown>) => {
props.onClose(
Object.fromEntries(Object.entries(newConfig).filter(checkOptions)),
const cfg = Object.fromEntries(
Object.entries(newConfig).filter(checkOptions),
);
if (!shallowEqual(cfg, props.config)) {
props.onClose(cfg);
} else {
props.onClose();
}
},
[props.onClose],
[props.onClose, props.config],
);

return (
Expand Down
21 changes: 1 addition & 20 deletions packages/website/src/components/hooks/useHashState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { useCallback, useEffect, useState } from 'react';
import type { CompilerFlags, ConfigModel, RulesRecord } from '../types';

import * as lz from 'lzstring.ts';
import { shallowEqual } from '../lib/shallowEqual';

function writeQueryParam(value: string): string {
return lz.LZString.compressToEncodedURIComponent(value);
Expand Down Expand Up @@ -76,26 +77,6 @@ const writeStateToUrl = (newState: ConfigModel): string => {
return '';
};

function shallowEqual(
object1: Record<string, unknown> | ConfigModel | undefined,
object2: Record<string, unknown> | ConfigModel | undefined,
): boolean {
if (object1 === object2) {
return true;
}
const keys1 = Object.keys(object1 ?? {});
const keys2 = Object.keys(object2 ?? {});
if (keys1.length !== keys2.length) {
return false;
}
for (const key of keys1) {
if (object1![key] !== object2![key]) {
return false;
}
}
return true;
}

function useHashState(
initialState: ConfigModel,
): [ConfigModel, (cfg: Partial<ConfigModel>) => void] {
Expand Down
21 changes: 21 additions & 0 deletions packages/website/src/components/lib/shallowEqual.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import type { ConfigModel } from '@site/src/components/types';

export function shallowEqual(
object1: Record<string, unknown> | ConfigModel | undefined,
object2: Record<string, unknown> | ConfigModel | undefined,
): boolean {
if (object1 === object2) {
return true;
}
const keys1 = Object.keys(object1 ?? {});
const keys2 = Object.keys(object2 ?? {});
if (keys1.length !== keys2.length) {
return false;
}
for (const key of keys1) {
if (object1![key] !== object2![key]) {
return false;
}
}
return true;
}
3 changes: 2 additions & 1 deletion packages/website/src/components/modals/Modal.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@
}

.modalClose {
cursor: pointer;
transition: color var(--ifm-transition-fast)
var(--ifm-transition-timing-default);
}

.modalClose:hover,
Expand Down
42 changes: 33 additions & 9 deletions packages/website/src/components/modals/Modal.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/* eslint-disable jsx-a11y/click-events-have-key-events,jsx-a11y/no-static-element-interactions */
import React from 'react';
import React, { MouseEvent, useCallback, useEffect } from 'react';
import clsx from 'clsx';
import styles from './Modal.module.css';
import CloseIcon from '../icons/CloseIcon';
Expand All @@ -12,24 +12,48 @@ interface ModalProps {
}

function Modal(props: ModalProps): JSX.Element {
useEffect(() => {
const closeOnEscapeKeyDown = (e: KeyboardEvent): void => {
if (e.key === 'Escape' || e.keyCode === 27) {
props.onClose();
}
};

document.body.addEventListener('keydown', closeOnEscapeKeyDown);
return (): void => {
document.body.removeEventListener('keydown', closeOnEscapeKeyDown);
};
}, []);

const onClick = useCallback(
(e: MouseEvent<HTMLDivElement>) => {
if (e.currentTarget === e.target) {
props.onClose();
}
},
[props.onClose],
);

return (
<div
className={clsx(styles.modal, props.isOpen ? styles.open : '')}
onClick={props.onClose}
onClick={onClick}
>
<div
role="dialog"
aria-modal="true"
className={clsx(styles.modalContent, 'item shadow--md')}
onClick={(e): void => {
e.stopPropagation();
}}
>
<div className={styles.modalHeader}>
<h2>{props.header}</h2>
<CloseIcon
className={styles.modalClose}
<button
aria-label="Close"
onClick={props.onClose}
size={22}
/>
className={clsx(styles.modalClose, 'clean-btn')}
type="button"
>
<CloseIcon size={22} />
</button>
</div>
<div className={styles.modalBody}>
{React.Children.map(props.children, child => child)}
Expand Down