From 3c45437702df633425d28d1aeaec6d82607b2bc6 Mon Sep 17 00:00:00 2001 From: Thomas Jacob Date: Wed, 28 Mar 2018 16:38:56 +0200 Subject: [PATCH] feat: placeholder pattern (fixes #13) --- src/store/styleguide/pattern.ts | 5 + .../styleguide/property/asset-property.ts | 5 + src/store/styleguide/styleguide.ts | 19 ++- src/styleguide/renderer/react/placeholder.tsx | 13 ++ src/styleguide/renderer/react/preview.tsx | 123 +++++++++++------- 5 files changed, 114 insertions(+), 51 deletions(-) create mode 100644 src/styleguide/renderer/react/placeholder.tsx diff --git a/src/store/styleguide/pattern.ts b/src/store/styleguide/pattern.ts index bfac26fa1..c7fa05c35 100644 --- a/src/store/styleguide/pattern.ts +++ b/src/store/styleguide/pattern.ts @@ -13,6 +13,11 @@ import { Store } from '../store'; * e.g. Patternplate. */ export class Pattern { + /** + * The ID of the synthetic asset content pattern. + */ + public static SYNTHETIC_ASSET_ID: string = 'synthetic:asset'; + /** * The ID of the synthetic text content pattern. */ diff --git a/src/store/styleguide/property/asset-property.ts b/src/store/styleguide/property/asset-property.ts index 5dc782503..2dba1f3a8 100644 --- a/src/store/styleguide/property/asset-property.ts +++ b/src/store/styleguide/property/asset-property.ts @@ -17,6 +17,11 @@ import { Property } from './property'; * @see AssetProperty.getValueFromUrl */ export class AssetProperty extends Property { + /** + * The ID of the synthetic string property in the synthetic asset content pattern. + */ + public static SYNTHETIC_ASSET_ID: string = 'asset'; + /** * Creates a new asset property. * @param id The technical ID of this property (e.g. the property name diff --git a/src/store/styleguide/styleguide.ts b/src/store/styleguide/styleguide.ts index 6fe9b9ad6..156a306de 100644 --- a/src/store/styleguide/styleguide.ts +++ b/src/store/styleguide/styleguide.ts @@ -1,3 +1,4 @@ +import { AssetProperty } from './property/asset-property'; import { Directory } from '../../styleguide/analyzer/directory'; import { PatternFolder } from './folder'; import * as PathUtils from 'path'; @@ -73,17 +74,25 @@ export class Styleguide { } /** - * Adds Alva's synthetic patterns to this styleguide. Synthetic patterns do not have a physical implementation. They are required to create page elements that represent values only, such as child text nodes. + * Adds Alva's synthetic patterns to this styleguide. + * Synthetic patterns do not have a physical implementation. + * They are required to create page elements that represent values only, + * such as child text nodes. */ private addSyntheticPatterns(): void { - const textPattern = new Pattern(Pattern.SYNTHETIC_TEXT_ID, 'text', ''); + const folder = new PatternFolder('synthetic', this.patternRoot); + + const textPattern = new Pattern(Pattern.SYNTHETIC_TEXT_ID, 'Text', ''); const textProperty = new StringProperty(StringProperty.SYNTHETIC_TEXT_ID); textPattern.addProperty(textProperty); - - const folder = new PatternFolder('synthetic', this.patternRoot); folder.addPattern(textPattern); - this.addPattern(textPattern); + + const assetPattern = new Pattern(Pattern.SYNTHETIC_ASSET_ID, 'Placeholder', ''); + const assetProperty = new AssetProperty(AssetProperty.SYNTHETIC_ASSET_ID); + assetPattern.addProperty(assetProperty); + folder.addPattern(assetPattern); + this.addPattern(assetPattern); } /** diff --git a/src/styleguide/renderer/react/placeholder.tsx b/src/styleguide/renderer/react/placeholder.tsx new file mode 100644 index 000000000..63d28a4bc --- /dev/null +++ b/src/styleguide/renderer/react/placeholder.tsx @@ -0,0 +1,13 @@ +import * as React from 'react'; + +export interface PlaceholderProps { + src?: string; +} + +export const Placeholder: React.StatelessComponent = props => { + if (props.src === undefined || props.src === null || props.src === '') { + return null; + } + + return ; +}; diff --git a/src/styleguide/renderer/react/preview.tsx b/src/styleguide/renderer/react/preview.tsx index 963753024..2ecfa729d 100644 --- a/src/styleguide/renderer/react/preview.tsx +++ b/src/styleguide/renderer/react/preview.tsx @@ -1,3 +1,4 @@ +import { AssetProperty } from '../../../store/styleguide/property/asset-property'; import { ErrorMessage } from './error-message'; import { HighlightArea } from '../highlight-area'; import { observable } from 'mobx'; @@ -5,6 +6,7 @@ import { observer } from 'mobx-react'; import { Page } from '../../../store/page/page'; import { PageElement } from '../../../store/page/page-element'; import { Pattern } from '../../../store/styleguide/pattern'; +import { Placeholder } from './placeholder'; import { PropertyValue } from '../../../store/page/property-value'; import * as React from 'react'; import * as ReactDOM from 'react-dom'; @@ -25,7 +27,7 @@ interface PatternWrapperState { } interface PatternWrapperProps { - pattern: Pattern; + element: PageElement; } class PatternWrapper extends React.Component { @@ -40,12 +42,8 @@ class PatternWrapper extends React.Component - ); + const pattern = this.props.element.getPattern() as Pattern; + return ; } else { return this.props.children; } @@ -65,6 +63,24 @@ class Preview extends React.Component { this.highlightArea = new HighlightArea(); } + // tslint:disable-next-line:no-any + private collectChildren(componentProps: any, pageElement: PageElement): void { + componentProps.children = pageElement + .getChildren() + .map((child, index) => this.createComponent(child)); + } + + // tslint:disable-next-line:no-any + private collectPropertyValues(componentProps: any, pageElement: PageElement): void { + const pattern = pageElement.getPattern() as Pattern; + pattern.getProperties().forEach(property => { + const propertyId = property.getId(); + componentProps[propertyId] = this.createComponent( + property.convertToRender(pageElement.getPropertyValue(propertyId)) + ); + }); + } + public componentDidMount(): void { this.triggerHighlight(); } @@ -75,6 +91,11 @@ class Preview extends React.Component { } } + private createAssetComponent(pageElement: PageElement): JSX.Element { + const src = pageElement.getPropertyValue(AssetProperty.SYNTHETIC_ASSET_ID) as string; + return this.createWrapper(pageElement, ); + } + /** * Converts a JSON-serializable declaration of a pattern, primitive, or collection * into a React component (or primitive), deep-traversing through properties and children. @@ -84,7 +105,7 @@ class Preview extends React.Component { * @returns A React component in case of a page element, the primitive in case of a primitive, * or an array or object with values converted in the same manner, if an array resp. object is provided. */ - private createComponent(value: PropertyValue, key?: string): JSX.Element | PropertyValue { + private createComponent(value: PropertyValue): JSX.Element | PropertyValue { if (value === undefined || value === null || typeof value !== 'object') { // Primitives stay primitives. return value; @@ -103,51 +124,24 @@ class Preview extends React.Component { try { const patternId: string = pattern.getId(); if (patternId === Pattern.SYNTHETIC_TEXT_ID) { - return pageElement.getPropertyValue(StringProperty.SYNTHETIC_TEXT_ID); + return this.createStringComponent(pageElement); + } else if (patternId === Pattern.SYNTHETIC_ASSET_ID) { + return this.createAssetComponent(pageElement); } // tslint:disable-next-line:no-any const componentProps: any = {}; - pattern.getProperties().forEach(property => { - const propertyId = property.getId(); - - componentProps[propertyId] = this.createComponent( - property.convertToRender(pageElement.getPropertyValue(propertyId)), - propertyId - ); - }); - - componentProps.children = pageElement - .getChildren() - .map((child, index) => this.createComponent(child, String(index))); + this.collectPropertyValues(componentProps, pageElement); + this.collectChildren(componentProps, pageElement); // Then, load the pattern factory - const patternPath: string = pattern.getImplementationPath(); - let patternFactory: React.StatelessComponent | ObjectConstructor = this - .patternFactories[patternId]; - if (patternFactory == null) { - const exportName = pattern.getExportName(); - const module = require(patternPath); - patternFactory = module[exportName]; - this.patternFactories[patternId] = patternFactory; - } + const patternFactory: + | React.StatelessComponent + | ObjectConstructor = this.loadAndCachePatternFactory(pattern); - const reactComponent = React.createElement(patternFactory, componentProps); - - // Finally, build the component - return ( - (this.patternWrapperRef = ref) - : undefined - } - > - {reactComponent} - - ); + // Finally, build the component and wrap it for selectability + const reactElement = React.createElement(patternFactory, componentProps); + return this.createWrapper(pageElement, reactElement); } catch (error) { return ; } @@ -165,6 +159,43 @@ class Preview extends React.Component { } } + private createStringComponent(pageElement: PageElement): string { + return String(pageElement.getPropertyValue(StringProperty.SYNTHETIC_TEXT_ID)); + } + + private createWrapper(pageElement: PageElement, reactElement: JSX.Element): JSX.Element { + return ( + (this.patternWrapperRef = ref) + : undefined + } + > + {reactElement} + + ); + } + + private loadAndCachePatternFactory( + pattern: Pattern + ): React.StatelessComponent | ObjectConstructor { + let patternFactory: React.StatelessComponent | ObjectConstructor = this.patternFactories[ + pattern.getId() + ]; + if (patternFactory == null) { + const patternPath: string = pattern.getImplementationPath(); + const exportName = pattern.getExportName(); + const module = require(patternPath); + patternFactory = module[exportName]; + this.patternFactories[pattern.getId()] = patternFactory; + } + + return patternFactory; + } + public render(): JSX.Element | null { if (this.props.page) { const highlightAreaProps = this.highlightArea.getProps();