Skip to content

Commit

Permalink
Add inline preview feature
Browse files Browse the repository at this point in the history
  • Loading branch information
siarheiy committed Mar 19, 2024
1 parent 9c1e9ed commit f22f509
Show file tree
Hide file tree
Showing 14 changed files with 249 additions and 74 deletions.
39 changes: 28 additions & 11 deletions app/src/common/docs/componentEditor/ComponentEditor.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
import * as React from 'react';
import {
IComponentDocs,
TDocConfig,
TSkin,
TDocsGenExportedType,
useDocBuilderGen,
PropDocPropsUnknown,
PropDocUnknown,
PropDocUnknown, TDocContext, DocBuilder,
} from '@epam/uui-docs';
import { ComponentEditorView } from './view/ComponentEditorView';
import { getSkin } from './utils';
Expand All @@ -22,6 +21,7 @@ import {
updatePropInputData,
} from './propDocUtils';
import { useQuery } from '../../../helpers';
import { buildPreviewRef } from './previewLinkUtils';

export function ComponentEditorWrapper(props: {
theme: TTheme,
Expand All @@ -40,8 +40,7 @@ export function ComponentEditorWrapper(props: {
const componentId = useQuery('id');
const skin = getSkin(theme, isSkin);
const { isLoaded, docs, generatedFromType } = useDocBuilderGen({ config, skin, loadDocsGenType });
const supportsPreview = !!docs?.docPreview;
const previewLink = supportsPreview ? `/preview?theme=${theme}&isSkin=${isSkin}&componentId=${componentId}` : undefined;

React.useEffect(() => {
if (!config) {
onRedirectBackToDocs();
Expand All @@ -50,22 +49,26 @@ export function ComponentEditorWrapper(props: {

return (
<ComponentEditor
isSkin={ isSkin }
theme={ theme }
componentId={ componentId }
isLoaded={ isLoaded }
onRedirectBackToDocs={ onRedirectBackToDocs }
docs={ docs }
title={ title }
skin={ skin }
generatedFromType={ generatedFromType }
previewLink={ previewLink }
/>
);
}

interface ComponentEditorProps {
docs?: IComponentDocs<PropDocPropsUnknown>;
docs?: DocBuilder<PropDocPropsUnknown>;
skin: TSkin;
title: string;
previewLink: string | undefined;
isSkin: boolean;
theme: TTheme;
componentId: string;
isLoaded: boolean;
onRedirectBackToDocs: () => void;
generatedFromType?: TDocsGenExportedType;
Expand Down Expand Up @@ -226,16 +229,29 @@ export class ComponentEditor extends React.Component<ComponentEditorProps, Compo
this.setState({ selectedContext });
};

handleBuildPreviewRef = () => {
const { isSkin, theme, componentId, docs } = this.props;
const { inputData } = this.state;
const context = this.getSelectedCtxName() as TDocContext;
return buildPreviewRef({ context, inputData, isSkin, theme, componentId, docs });
};

getSelectedCtxName = () => {
const { docs } = this.props;
const { selectedContext } = this.state;
const { contexts } = docs || {};
return selectedContext || (contexts?.length > 0 ? contexts[0].name : undefined);
};

render() {
const { title, docs, isLoaded, onRedirectBackToDocs, generatedFromType, previewLink } = this.props;
const { inputData, selectedContext, isInited, componentKey } = this.state;
const { title, docs, isLoaded, onRedirectBackToDocs, generatedFromType } = this.props;
const { inputData, isInited, componentKey } = this.state;
const { component: DemoComponent, name: tagName, contexts, props } = docs || {};
const selectedCtxName = selectedContext || (contexts?.length > 0 ? contexts[0].name : undefined);
const selectedCtxName = this.getSelectedCtxName();
const isDocUnsupportedForSkin = isLoaded && !docs;

return (
<ComponentEditorView
previewLink={ previewLink }
contexts={ contexts }
componentKey={ componentKey }
DemoComponent={ DemoComponent }
Expand All @@ -249,6 +265,7 @@ export class ComponentEditor extends React.Component<ComponentEditorProps, Compo
selectedCtxName={ selectedCtxName }
tagName={ tagName }
title={ title }
onBuildPreviewRef={ this.handleBuildPreviewRef }
onChangeSelectedCtx={ this.handleChangeContext }
onPropExampleIdChange={ this.handlePropExampleIdChange }
onPropValueChange={ this.handlePropValueChange }
Expand Down
90 changes: 90 additions & 0 deletions app/src/common/docs/componentEditor/previewLinkUtils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import { DocBuilder, PropDocPropsUnknown, TDocContext, TPreviewPropsItem } from '@epam/uui-docs';
import { TPropInputDataAll } from './propDocUtils';
import { TTheme } from '../docsConstants';

export type TPreviewRef = { link: string, error: string | undefined };
type TBuildPreviewLinkParams = {
context: TDocContext,
inputData: TPropInputDataAll,
theme: TTheme,
isSkin: boolean,
componentId: string,
docs: DocBuilder<PropDocPropsUnknown>
};

const INLINE_PP_PREFIX = 'inline:';

export function buildPreviewRef(params: TBuildPreviewLinkParams): TPreviewRef {
const { context, inputData, theme, isSkin, componentId, docs } = params;
const unableToSerialize: string[] = [];
const initialValue = {
id: '',
context,
matrix: {},
};

const previewProps = Object.keys(inputData).reduce<TPreviewPropsItem<any>>((acc, name) => {
const { value, exampleId } = inputData[name];
if (exampleId !== undefined) {
const exObject = Object.values(docs.getPropExamplesMap(name)).find(({ id }) => exampleId === id);
if (exObject) {
if (!exObject?.isDefault) {
Object.assign(acc.matrix, {
[name]: {
examples: [exObject.name],
},
});
}
} else {
console.error(`Unable to find example of property=${name} by exampleId=${exampleId}. The property will be ignored.`);
}
} else if (value !== undefined) {
if (['string', 'boolean', 'number'].indexOf(typeof value) !== -1) {
Object.assign(acc.matrix, {
[name]: {
values: [value],
},
});
} else {
unableToSerialize.push(name);
}
}
return acc;
}, initialValue);

const link = `/preview?theme=${theme}&isSkin=${isSkin}&componentId=${componentId}&previewId=${encodeInlinePreviewPropsForUrl(previewProps)}`;
let error;
if (unableToSerialize.length) {
error = `Next props cannot be serialized for URL and will be excluded: ${unableToSerialize.join(', ')}.`;
}
return { link, error };
}

function encodeInlinePreviewPropsForUrl(pp: object) {
const str = JSON.stringify(pp);
return encodeURIComponent(`${INLINE_PP_PREFIX}${str}`);
}

export function isPreviewIdInline(previewId: string) {
return previewId.indexOf('inline:') === 0;
}

export function decodeInlinePreviewIdFromUrl(previewId: string): TPreviewPropsItem<unknown> {
if (isPreviewIdInline(previewId)) {
const json = previewId.substring(INLINE_PP_PREFIX.length);
try {
return JSON.parse(json) as TPreviewPropsItem<unknown>;
} catch (err) {
console.error(err);
}
}
throw new Error(`Unsupported format of inline preview props: ${previewId}`);
}

export function formatPreviewId(previewId: string): string {
if (isPreviewIdInline(previewId)) {
const decoded = decodeInlinePreviewIdFromUrl(previewId);
return JSON.stringify(decoded, undefined, 1);
}
return previewId;
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { DemoErrorBoundary } from './DemoErrorBoundary';
import css from './ComponentEditorView.module.scss';
import { PeTable } from './peTable/PeTable';
import { buildNormalizedInputValuesMap } from '../propDocUtils';
import { TPreviewRef } from '../previewLinkUtils';

type TInputData<TProps> = {
[name in keyof TProps]: {
Expand Down Expand Up @@ -42,7 +43,7 @@ interface IComponentEditorViewProps<TProps> {
onClearProp: (name: keyof TProps) => void;
onPropValueChange: (params: { prop: PropDoc<TProps, keyof TProps>, newValue: TProps[keyof TProps] }) => void;
onPropExampleIdChange: (params: { prop: PropDoc<TProps, keyof TProps>, newExampleId: string | undefined }) => void;
previewLink: string | undefined;
onBuildPreviewRef: () => TPreviewRef;
}
export function ComponentEditorView<TProps = PropDocPropsUnknown>(props: IComponentEditorViewProps<TProps>) {
const demoComponentProps = React.useMemo(() => {
Expand Down Expand Up @@ -80,7 +81,7 @@ export function ComponentEditorView<TProps = PropDocPropsUnknown>(props: ICompon
propDoc={ props.propDoc }
title={ props.title }
typeRef={ props.generatedFromType }
previewLink={ props.previewLink }
previewRef={ props.onBuildPreviewRef() }
>
<DemoCode
demoComponentProps={ demoComponentProps }
Expand Down
35 changes: 29 additions & 6 deletions app/src/common/docs/componentEditor/view/peTable/PeTable.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
import * as React from 'react';
import { FlexCell, FlexRow, FlexSpacer, IconButton, ScrollBars, Text, Tooltip } from '@epam/uui';
import {
FlexCell,
FlexRow,
FlexSpacer,
IconButton,
ScrollBars,
Text,
Tooltip, WarningAlert,
} from '@epam/uui';
import { PeTableRow } from './PeTableRow';
import { IPeTableProps } from './types';
import { ReactComponent as ResetIcon } from '../../../../../icons/reset-icon.svg';
Expand Down Expand Up @@ -35,7 +43,7 @@ export function PeTable<TProps>(props: IPeTableProps<TProps>) {
return (
<div className={ css.container }>
<PeTableToolbar
previewLink={ props.previewLink }
previewRef={ props.previewRef }
tooltip={ props.typeRef }
title={ props.title }
onResetAllProps={ props.onResetAllProps }
Expand All @@ -53,7 +61,22 @@ export function PeTable<TProps>(props: IPeTableProps<TProps>) {
PeTable.displayName = 'PeTable';

const PeTableToolbar = React.memo(
function PeTableToolbarComponent<TProps>({ title, onResetAllProps, tooltip, previewLink }: Pick<IPeTableProps<TProps>, 'title' | 'onResetAllProps' | 'previewLink'> & { tooltip: string }) {
function PeTableToolbarComponent<TProps>({ title, onResetAllProps, tooltip, previewRef }: Pick<IPeTableProps<TProps>, 'title' | 'onResetAllProps' | 'previewRef'> & { tooltip: string }) {
const renderPreviewTooltipContent = () => {
const err = previewRef.error;
if (err) {
return (
<>
<Text size="30">Open Preview</Text>
<WarningAlert icon={ null }>
<Text size="30">{ err }</Text>
</WarningAlert>
</>
);
}
return 'Open Preview';
};

return (
<FlexRow key="head" size="36" padding="12" borderBottom columnGap="6" cx={ css.boxSizing }>
<Tooltip content={ tooltip }>
Expand All @@ -62,12 +85,12 @@ const PeTableToolbar = React.memo(
</Text>
</Tooltip>
<FlexSpacer />
{ previewLink && (
<Tooltip placement="auto" content="Open Preview">
{ previewRef && (
<Tooltip placement="auto" color="neutral" content={ renderPreviewTooltipContent() }>
<IconButton
target="_blank"
icon={ PreviewIcon }
href={ previewLink }
href={ previewRef.link }
color="info"
/>
</Tooltip>
Expand Down
3 changes: 2 additions & 1 deletion app/src/common/docs/componentEditor/view/peTable/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {
IPropSamplesCreationContext,
PropDoc, TDocsGenExportedType,
} from '@epam/uui-docs';
import { TPreviewRef } from '../../previewLinkUtils';

export interface IPeTableProps<TProps> {
inputData: {
Expand All @@ -20,7 +21,7 @@ export interface IPeTableProps<TProps> {
propDoc: PropDoc<TProps, keyof TProps>[]
title: string;
typeRef: TDocsGenExportedType;
previewLink: string | undefined;
previewRef: TPreviewRef;
}

export interface IPeTableRowProps<TProps> {
Expand Down
Loading

0 comments on commit f22f509

Please sign in to comment.