Skip to content
This repository has been archived by the owner on Oct 23, 2023. It is now read-only.

Commit

Permalink
feat: placeholder pattern (fixes #13)
Browse files Browse the repository at this point in the history
  • Loading branch information
TheReincarnator authored and lkuechler committed Apr 4, 2018
1 parent 0e7154a commit 3c45437
Show file tree
Hide file tree
Showing 5 changed files with 114 additions and 51 deletions.
5 changes: 5 additions & 0 deletions src/store/styleguide/pattern.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*/
Expand Down
5 changes: 5 additions & 0 deletions src/store/styleguide/property/asset-property.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
19 changes: 14 additions & 5 deletions src/store/styleguide/styleguide.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -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);
}

/**
Expand Down
13 changes: 13 additions & 0 deletions src/styleguide/renderer/react/placeholder.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import * as React from 'react';

export interface PlaceholderProps {
src?: string;
}

export const Placeholder: React.StatelessComponent<PlaceholderProps> = props => {
if (props.src === undefined || props.src === null || props.src === '') {
return null;
}

return <img src={props.src} />;
};
123 changes: 77 additions & 46 deletions src/styleguide/renderer/react/preview.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import { AssetProperty } from '../../../store/styleguide/property/asset-property';
import { ErrorMessage } from './error-message';
import { HighlightArea } from '../highlight-area';
import { observable } from 'mobx';
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';
Expand All @@ -25,7 +27,7 @@ interface PatternWrapperState {
}

interface PatternWrapperProps {
pattern: Pattern;
element: PageElement;
}

class PatternWrapper extends React.Component<PatternWrapperProps, PatternWrapperState> {
Expand All @@ -40,12 +42,8 @@ class PatternWrapper extends React.Component<PatternWrapperProps, PatternWrapper

public render(): React.ReactNode {
if (this.state.errorMessage) {
return (
<ErrorMessage
patternName={this.props.pattern.getName()}
error={this.state.errorMessage}
/>
);
const pattern = this.props.element.getPattern() as Pattern;
return <ErrorMessage patternName={pattern.getName()} error={this.state.errorMessage} />;
} else {
return this.props.children;
}
Expand All @@ -65,6 +63,24 @@ class Preview extends React.Component<PreviewProps> {
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();
}
Expand All @@ -75,6 +91,11 @@ class Preview extends React.Component<PreviewProps> {
}
}

private createAssetComponent(pageElement: PageElement): JSX.Element {
const src = pageElement.getPropertyValue(AssetProperty.SYNTHETIC_ASSET_ID) as string;
return this.createWrapper(pageElement, <Placeholder src={src} />);
}

/**
* Converts a JSON-serializable declaration of a pattern, primitive, or collection
* into a React component (or primitive), deep-traversing through properties and children.
Expand All @@ -84,7 +105,7 @@ class Preview extends React.Component<PreviewProps> {
* @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;
Expand All @@ -103,51 +124,24 @@ class Preview extends React.Component<PreviewProps> {
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 (
<PatternWrapper
key={key}
pattern={pattern}
ref={
pageElement.getId() === this.props.selectedElementId
? (ref: PatternWrapper) => (this.patternWrapperRef = ref)
: undefined
}
>
{reactComponent}
</PatternWrapper>
);
// Finally, build the component and wrap it for selectability
const reactElement = React.createElement(patternFactory, componentProps);
return this.createWrapper(pageElement, reactElement);
} catch (error) {
return <ErrorMessage patternName={pattern.getName()} error={error.toString()} />;
}
Expand All @@ -165,6 +159,43 @@ class Preview extends React.Component<PreviewProps> {
}
}

private createStringComponent(pageElement: PageElement): string {
return String(pageElement.getPropertyValue(StringProperty.SYNTHETIC_TEXT_ID));
}

private createWrapper(pageElement: PageElement, reactElement: JSX.Element): JSX.Element {
return (
<PatternWrapper
key={pageElement.getId()}
element={pageElement}
ref={
pageElement.getId() === this.props.selectedElementId
? (ref: PatternWrapper) => (this.patternWrapperRef = ref)
: undefined
}
>
{reactElement}
</PatternWrapper>
);
}

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();
Expand Down

0 comments on commit 3c45437

Please sign in to comment.