Skip to content
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

EZP-28841: Content On the Fly v2 #55

Merged
merged 1 commit into from
Mar 12, 2018
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
4 changes: 2 additions & 2 deletions Resources/public/js/MultiFileUpload.module.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion Resources/public/js/MultiFileUpload.module.js.map

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions Resources/public/js/SubItems.module.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion Resources/public/js/SubItems.module.js.map

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions Resources/public/js/UniversalDiscovery.module.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion Resources/public/js/UniversalDiscovery.module.js.map

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';

import { loadLocation } from '../../services/universal.discovery.service';

import './css/content.creator.component.css';

export default class ContentCreatorComponent extends Component {
constructor(props) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you're not using props to initialize state, then you can omit the props variable in the constructor method.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can but I don't think you should.
Class components should always call the base constructor with props. from React doc: https://reactjs.org/docs/state-and-lifecycle.html#adding-local-state-to-a-class

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is recommendation, right. We can follow this rule. It's especially useful when you're extending your own component classes. In this case, when you extend base component class it's not that relevant. Because any kind of props won't affect the way the base class works.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know it will not cause any problems when extending React.Component for now, but if they have this in their doc they may require this at some point in the future.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, let's keep it then. From the currect perspective, it brings no value now, but it's a recommended by React doc.

super(props);

this.handleIframeLoad = this.handleIframeLoad.bind(this);
this.handlePublish = this.handlePublish.bind(this);

this.state = {
iframeLoading: true
};
}

handlePublish() {
this.iframe.contentWindow.onbeforeunload = () => {};
this.iframe.contentWindow.document.body.querySelector('#ezrepoforms_content_edit_publish').click();
}

handleIframeLoad() {
const locationId = this.iframe.contentWindow.document.querySelector('meta[name="LocationID"]');
const iframeUrl = this.generateIframeUrl();

if (this.iframe.contentWindow.location.pathname !== iframeUrl && !locationId) {
this.iframe.setAttribute('src', iframeUrl);

return;
}

if (locationId) {
this.loadLocationInfo(locationId.content);
} else {
this.setState(state => Object.assign({}, state, { iframeLoading: false }));

this.iframe.contentWindow.onbeforeunload = () => {
return '';
};
this.iframe.contentWindow.onunload = () => {
this.setState(state => Object.assign({}, state, { iframeLoading: true }));
};
}
}

loadLocationInfo(locationId) {
const {loadLocation, handlePublish, restInfo} = this.props;
const promise = new Promise(resolve => loadLocation(
Object.assign({}, restInfo, { locationId }),
resolve
));

promise
.then((response) => {
handlePublish(response.View.Result.searchHits.searchHit[0].value.Location);
});
}

generateIframeUrl() {
const {selectedLocationId, selectedLanguage, selectedContentType} = this.props;

return window.Routing.generate('ezplatform.content_on_the_fly.create', {
locationId: selectedLocationId,
languageCode: selectedLanguage.languageCode,
contentTypeIdentifier: selectedContentType.identifier
});
}

/**
* Renders a loading state spinner
*
* @method renderLoadingSpinner
* @returns {Element}
* @memberof FinderTreeLeafComponent
*/
renderLoadingSpinner() {
if (!this.state.iframeLoading) {
return null;
}

return (
<svg className="ez-icon ez-spin ez-icon-x2 ez-icon-spinner">
<use xlinkHref="/bundles/ezplatformadminui/img/ez-icons.svg#spinner"></use>
</svg>
);
}

render() {
const {labels, selectedContentType, selectedLanguage, maxHeight, onCancel} = this.props;
const title = labels.contentOnTheFly.creatingContent
.replace('{contentType}', selectedContentType.name)
.replace('{language}', selectedLanguage.name);
const iframeUrl = this.generateIframeUrl();
const contentClass = this.state.iframeLoading ? 'm-ud__content is-loading' : 'm-ud__content';

return (
<div className="m-ud__wrapper">
<div className="m-ud c-content-creator">
<h1 className="m-ud__title">{title}</h1>
<div className="m-ud__content-wrapper">
<div className={contentClass} ref={ref => this._refContentContainer = ref}>
{this.renderLoadingSpinner()}
<iframe
src={iframeUrl}
ref={ref => this.iframe = ref}
className="c-content-creator__iframe"
onLoad={this.handleIframeLoad}
style={{height:`${maxHeight + 32}px`}} />
</div>
<div className="m-ud__actions">
<div className="m-ud__btns">
<button className="m-ud__action--cancel" onClick={onCancel}>{labels.udw.cancel}</button>
<button className="m-ud__action--publish" onClick={this.handlePublish}>{labels.contentOnTheFly.publish}</button>
</div>
</div>
</div>
</div>
</div>
);
}
}

ContentCreatorComponent.propTypes = {
maxHeight: PropTypes.number.isRequired,
labels: PropTypes.object.isRequired,
selectedLanguage: PropTypes.object.isRequired,
selectedContentType: PropTypes.object.isRequired,
selectedLocationId: PropTypes.number.isRequired,
onCancel: PropTypes.func.isRequired,
handlePublish: PropTypes.func.isRequired,
loadLocation: PropTypes.func,
restInfo: PropTypes.object.isRequired
};

ContentCreatorComponent.defaultProps = {
loadLocation
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
.c-content-creator .m-ud__content-wrapper {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe we agreed to move the CSS code directly into SASS files of a bundle. We are not importing CSS into the components anymore.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought this was applied only to Page Builder. If we move CSS to admin-ui sass we would always need to make two PR when changing something in modules

padding: 0 0 16px 0;
grid-template-rows: 1fr 64px;
grid-template-areas: 'main' 'footer';
}

.c-content-creator .m-ud__content {
padding: 0
}

.c-content-creator .m-ud__actions {
padding-right: 16px;
}

.c-content-creator .ez-icon-spinner {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}

.c-content-creator__iframe {
width: 100%;
border: none;
}

.m-ud__action--publish {
font-weight: 700;
background: #f15a10;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Recently, I've found out that we're exposing the CSS variables in the :root of our styles. Maybe we should start using CSS variables? So background: var(--orange);

}

.m-ud__action--publish:hover,
.m-ud__action--publish:focus {
background: #cb3400;
}

.m-ud__content {
position: relative;
}

.is-loading:before {
content: "";
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: #e3e3e3;
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import PropTypes from 'prop-types';

import './css/content.meta.preview.component.css';

const TAB_CREATE = 'create';

export default class ContentMetaPreviewComponent extends Component {
constructor() {
super();
Expand Down Expand Up @@ -87,6 +89,10 @@ export default class ContentMetaPreviewComponent extends Component {
* @memberof ContentMetaPreviewComponent
*/
renderSelectContentBtn() {
if (this.props.activeTab === TAB_CREATE) {
return null;
}

const {data, canSelectContent, onSelectContent, labels} = this.props;
const attrs = {
className: 'c-meta-preview__btn--select',
Expand Down Expand Up @@ -181,5 +187,6 @@ ContentMetaPreviewComponent.propTypes = {
lastModified: PropTypes.string.isRequired,
translations: PropTypes.string.isRequired
}).isRequired,
maxHeight: PropTypes.number.isRequired
maxHeight: PropTypes.number.isRequired,
activeTab: PropTypes.string.isRequired
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';

import './css/create.choose.content.type.component.css';

export default class ChooseContentTypeComponent extends Component {
constructor(props) {
super(props);

this._filterTimeout = null;

this.updateFilterQuery = this.updateFilterQuery.bind(this);
this.renderGroup = this.renderGroup.bind(this);
this.renderItem = this.renderItem.bind(this);

this.state = {
selected: {},
filterQuery: ''
};
}

updateSelectedItem(item) {
this.props.onContentTypeSelected(item);

this.setState(state => Object.assign({}, state, {selected: item}));
}

updateFilterQuery(event) {
const filterQuery = event.target.value.toLowerCase();

window.clearTimeout(this._filterTimeout);

this._filterTimeout = window.setTimeout(() => {
this.setState(state => Object.assign({}, state, { filterQuery }));
}, 200);
}

renderItem(item, index) {
const attrs = {
className: 'c-choose-content-type__group-item',
onClick: this.updateSelectedItem.bind(this, item),
key: index
};

if (this.state.selected.identifier === item.identifier) {
attrs.className = `${attrs.className} is-selected`;
}

if (this.state.filterQuery && !item.name.toLowerCase().includes(this.state.filterQuery)) {
attrs.hidden = true;
}

return (
<div {...attrs}>
{item.name}
</div>
);
}

renderGroup(groupName, index) {
const items = this.props.contentTypes[groupName];
const groupAttrs = {};

if (this.state.filterQuery && items.every(item => !item.name.toLowerCase().includes(this.state.filterQuery))) {
groupAttrs.hidden = true;
}

return (
<div className="c-choose-content-type__group" key={index}>
<div className="c-choose-content-type__group-name" {...groupAttrs}>
{groupName}
</div>
{items.map(this.renderItem)}
</div>
);
}

render() {
const {labels, maxHeight, contentTypes} = this.props;

return (
<div className="c-choose-content-type">
<p className="c-choose-content-type__title">{labels.contentOnTheFly.selectContentType}</p>
<div className="c-choose-content-type__list-wrapper">
<input className="form-control" type="text" placeholder={labels.contentOnTheFly.typeToRefine} onChange={this.updateFilterQuery} />
<div className="c-choose-content-type__list" style={{maxHeight:`${maxHeight - 232}px`}}>
{Object.keys(contentTypes).map(this.renderGroup)}
</div>
</div>
</div>
);
}
}

ChooseContentTypeComponent.propTypes = {
maxHeight: PropTypes.number.isRequired,
labels: PropTypes.object.isRequired,
contentTypes: PropTypes.object.isRequired,
onContentTypeSelected: PropTypes.func.isRequired
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';

import './css/create.choose.language.component.css';

export default class ChooseLanguageComponent extends Component {
constructor(props) {
super(props);

this.updateSelection = this.updateSelection.bind(this);
this.renderOption = this.renderOption.bind(this);

this.state = {
selectedLanguage: props.languages[0]
};
}

updateSelection(event) {
const languageCode = event.target.value;
const selectedLanguage = this.props.languages.find(language => language.languageCode === languageCode);

this.props.onLanguageSelected(selectedLanguage);

this.setState(state => Object.assign({}, state, { selectedLanguage }));
}

renderOption(item, index) {
const attrs = {
key: index,
value: item.languageCode
};

if (item.languageCode === this.props.forcedLanguage) {
attrs.selected = true;
}

return (
<option {...attrs}>{item.name}</option>
);
}

render() {
const selectAttrs = {
className: 'form-control',
onChange: this.updateSelection
};

if (this.props.forcedLanguage) {
selectAttrs.disabled = true;
}

return (
<div className="c-choose-language">
<p className="c-choose-language__title">{this.props.labels.contentOnTheFly.selectLanguage}</p>
<div className="c-choose-lagauge__select-wrapper">
<select {...selectAttrs}>
{this.props.languages.map(this.renderOption)}
</select>
</div>
</div>
);
}
}

ChooseLanguageComponent.propTypes = {
maxHeight: PropTypes.number.isRequired,
labels: PropTypes.object.isRequired,
languages: PropTypes.array.isRequired,
onLanguageSelected: PropTypes.func.isRequired,
forcedLanguage: PropTypes.string.isRequired
};
Loading