Skip to content

Commit

Permalink
PLANET-7305: Move Counter block into master-theme (#2176)
Browse files Browse the repository at this point in the history
* PLANET-7305: Move Counter block into master-theme

Ref: https://jira.greenpeace.org/browse/PLANET-7305

* Reflect P4 options and features

- From PHP to JS

* Follow up Cookies block

- Modify the p4_vars options

---------

Co-authored-by: Dan Tovbein <dtovbein@gmail.com>
  • Loading branch information
GP-Dan-Tovbein and dantovbein authored Dec 29, 2023
1 parent a4e1928 commit 70d54b9
Show file tree
Hide file tree
Showing 14 changed files with 630 additions and 10 deletions.
2 changes: 1 addition & 1 deletion assets/src/blocks/Cookies/CookiesFieldResetButton.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ const {__} = wp.i18n;

import {Tooltip} from '@wordpress/components';

const COOKIES_DEFAULT_COPY = window.p4_vars.cookies_default_copy || {};
const COOKIES_DEFAULT_COPY = window.p4_vars.options.cookies_default_copy || {};

export const CookiesFieldResetButton = ({fieldName, toAttribute, currentValue}) => {
const defaultValue = COOKIES_DEFAULT_COPY[fieldName] || '';
Expand Down
6 changes: 3 additions & 3 deletions assets/src/blocks/Cookies/CookiesFrontend.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,17 @@ const {__} = wp.i18n;

const dataLayer = window.dataLayer || [];

const COOKIES_DEFAULT_COPY = window.p4_vars.cookies_default_copy || {};
const COOKIES_DEFAULT_COPY = window.p4_vars.options.cookies_default_copy || {};

function gtag() {
dataLayer.push(arguments);
}

// Planet4 settings(Planet 4 > Cookies > Enable Analytical Cookies).
const ENABLE_ANALYTICAL_COOKIES = window.p4_vars.enable_analytical_cookies;
const ENABLE_ANALYTICAL_COOKIES = window.p4_vars.options.enable_analytical_cookies;

// Planet4 settings (Planet 4 > Analytics > Enable Google Consent Mode).
const ENABLE_GOOGLE_CONSENT_MODE = window.p4_vars.enable_google_consent_mode;
const ENABLE_GOOGLE_CONSENT_MODE = window.p4_vars.options.enable_google_consent_mode;

const CONSENT_COOKIE = 'greenpeace';
const NO_TRACK_COOKIE = 'no_track';
Expand Down
105 changes: 105 additions & 0 deletions assets/src/blocks/Counter/CounterBlock.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import {CounterEditor} from './CounterEditor';
import {frontendRendered} from '../frontendRendered';
import {CounterFrontend} from './CounterFrontend';
import {renderToString} from 'react-dom/server';

export const BLOCK_NAME = 'planet4-blocks/counter';

const attributes = {
title: {
type: 'string',
default: '',
},
description: {
type: 'string',
default: '',
},
completed: {
type: 'integer',
default: '',
},
completed_api: {
type: 'string',
default: '',
},
target: {
type: 'integer',
default: '',
},
text: {
type: 'string',
default: '',
},
style: { // Needed to convert existing blocks
type: 'string',
default: '',
},
};

export const registerCounterBlock = () => {
const {registerBlockType, unregisterBlockStyle, registerBlockStyle} = wp.blocks;
const {RawHTML} = wp.element;

registerBlockType(BLOCK_NAME, {
title: 'Counter',
icon: 'dashboard',
category: 'planet4-blocks',
attributes,
supports: {
html: false, // Disable "Edit as HTMl" block option.
},
// eslint-disable-next-line no-shadow
edit: CounterEditor,
save: props => {
const markup = renderToString(
<div
data-hydrate={BLOCK_NAME}
data-attributes={JSON.stringify(props.attributes)}
>
<CounterFrontend {...props.attributes} />
</div>
);
return <RawHTML>{markup}</RawHTML>;
},
deprecated: [
{
attributes,
save: frontendRendered(BLOCK_NAME),
},
{
attributes,
save() {
return null;
},
},
],
});

// Remove the default style since it's the same as "text only"
unregisterBlockStyle(BLOCK_NAME, 'default');

const styles = [
{
name: 'plain',
label: 'Text Only',
isDefault: true,
},
{
name: 'bar',
label: 'Progress Bar',
},
{
name: 'arc',
label: 'Progress Dial',
},
];

if (window.p4_vars.features.feature_engaging_networks) {
styles.push({
name: 'en-forms-bar',
label: 'Progress Bar inside EN Form',
});
}
// Add our custom styles
registerBlockStyle(BLOCK_NAME, styles);
};
100 changes: 100 additions & 0 deletions assets/src/blocks/Counter/CounterEditor.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import {InspectorControls, RichText} from '@wordpress/block-editor';
import {
TextControl,
TextareaControl,
PanelBody,
} from '@wordpress/components';
import {URLInput} from '../../block-editor/URLInput/URLInput';
import {CounterFrontend} from './CounterFrontend';

const {__} = wp.i18n;

export const CounterEditor = ({setAttributes, attributes, isSelected}) => {
const toAttribute = attributeName => value => setAttributes({[attributeName]: value});

const renderEdit = () => (
<>
<InspectorControls>
<PanelBody title={__('Settings', 'planet4-blocks-backend')}>
<div>
<TextControl
label={__('Completed', 'planet4-blocks-backend')}
placeholder={__('e.g. number of signatures', 'planet4-blocks-backend')}
type="number"
value={attributes.completed}
onChange={value => toAttribute('completed')(Number(value))}
min={0}
/>
</div>

<div>
<URLInput
label={__('Completed API URL', 'planet4-blocks-backend')}
placeholder={__('API URL of completed number. If filled in will override the \'Completed\' field', 'planet4-blocks-backend')}
value={attributes.completed_api}
onChange={toAttribute('completed_api')}
/>
</div>

<div>
<TextControl
label={__('Target', 'planet4-blocks-backend')}
placeholder={__('e.g. target no. of signatures', 'planet4-blocks-backend')}
type="number"
value={attributes.target}
onChange={value => toAttribute('target')(Number(value))}
min={0}
/>
</div>

<div>
<TextareaControl
label={__('Text', 'planet4-blocks-backend')}
placeholder={__('e.g. "signatures collected of %target%"', 'planet4-blocks-backend')}
value={attributes.text}
onChange={toAttribute('text')}
/>
</div>
<div className="sidebar-blocks-help">
{__('These placeholders can be used:', 'planet4-blocks-backend')}{' '}<code>%completed%</code>, <code>%target%</code>, <code>%remaining%</code>
</div>
</PanelBody>
</InspectorControls>
</>
);

const renderView = () => (
<>
<div className="counter-block">
<header>
<RichText
tagName="h2"
className="page-section-header"
placeholder={__('Enter title', 'planet4-blocks-backend')}
value={attributes.title}
onChange={toAttribute('title')}
withoutInteractiveFormatting
allowedFormats={[]}
/>
</header>
<RichText
tagName="p"
className="page-section-description"
placeholder={__('Enter description', 'planet4-blocks-backend')}
value={attributes.description}
onChange={toAttribute('description')}
withoutInteractiveFormatting
allowedFormats={['core/bold', 'core/italic']}
/>
</div>
<CounterFrontend isEditing {...attributes} />
</>
);

return (
<>
{isSelected ? renderEdit() : null}
{renderView()}
</>
);
};
3 changes: 3 additions & 0 deletions assets/src/blocks/Counter/CounterEditorScript.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import {registerCounterBlock} from './CounterBlock';

registerCounterBlock();
155 changes: 155 additions & 0 deletions assets/src/blocks/Counter/CounterFrontend.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
import {Component} from '@wordpress/element';
import {getStyleFromClassName} from '../../functions/getStyleFromClassName';

export class CounterFrontend extends Component {
constructor(props) {
super(props);
this.state = {
remaining: 0,
completed: 0,
};

this.calculateRemaining = this.calculateRemaining.bind(this);
this.getCounterText = this.getCounterText.bind(this);
}

componentDidMount() {
const {completed_api} = this.props;
// Calculate completed and remaining values depending on props
const counter = this;
counter.calculateRemaining();
// Add an eventListener to the window to enable instantly updating counters with supported APIs
if (completed_api && completed_api.startsWith('https://')) {
window.addEventListener('updateCounter', counter.calculateRemaining, false);
}
this.appendENForm();
}

componentWillUnmount() {
const {completed_api} = this.props;
const counter = this;
if (completed_api && completed_api.startsWith('https://')) {
window.removeEventListener('updateCounter', counter.calculateRemaining, false);
}
}

componentDidUpdate({target: prevTarget, completed: prevCompleted, completed_api: prevCompletedApi}) {
// Update completed and remaining values depending on props
const {target, completed, completed_api} = this.props;
if (target !== prevTarget || completed !== prevCompleted || completed_api !== prevCompletedApi) {
this.calculateRemaining();
}
this.appendENForm();
}

appendENForm() {
// Append the counter inside the En-Form
const counterBar = document.querySelector('.wp-block-planet4-blocks-counter .counter-style-en-forms-bar')?.cloneNode(true);
const enFormHeader = document.querySelector('.enform-extra-header-placeholder');

if (counterBar && enFormHeader) {
enFormHeader.innerHTML = '';
enFormHeader.append(counterBar);
}
}

calculateRemaining() {
const {completed_api} = this.props;
const target = Math.max(this.props.target, 0);
let completed = Math.max(this.props.completed, 0);
let remaining = 0;
if (completed_api && completed_api.startsWith('https://')) {
fetch(completed_api)
.then(response => response.json())
.then(({unique_count}) => {
if (unique_count) {
completed = Math.max(unique_count, 0);
this.setState({
completed,
remaining: Math.max(target - completed, 0),
});
}
}).catch(() => {
// eslint-disable-next-line no-console
console.log('Error: Fetching api response...');
});
} else {
remaining = Math.max(target - completed, 0);
this.setState({remaining, completed});
}
}

getCounterText() {
const {text, target} = this.props;
const {remaining, completed} = this.state;
const COUNTER_TEXT = {
'%completed%': `<span class="counter-target">${completed}</span>`,
'%target%': `<span class="counter-target">${target || 0}</span>`,
'%remaining%': `<span class="counter-target">${remaining}</span>`,
};

return text.replace(/%completed%|%target%|%remaining%/gi, match => COUNTER_TEXT[match]);
}

render() {
const {
className,
title,
description,
text,
target,
isEditing,
} = this.props;

const {completed} = this.state;

let style = this.props.style || 'plain'; // Needed to convert existing blocks
const styleClass = getStyleFromClassName(className);
if (styleClass) {
style = styleClass;
}
const arcLength = 31.5;

const percent = Math.min(target > 0 ? Math.round(completed / target * 100) : 0, 100);

let counterClassName = `block counter-block counter-style-${style} ${className ?? ''}`;
if (isEditing) {
counterClassName += ' editing';
}

return (
<section className={counterClassName}>
{title && !isEditing &&
<header>
<h2 className="page-section-header">{title}</h2>
</header>
}
{description && !isEditing &&
<p className="page-section-description" dangerouslySetInnerHTML={{__html: description}} />
}
<div className="content-counter">
{(style === 'bar' || style === 'en-forms-bar') &&
<div className="progress-container">
<div className={`progress-bar ${style === 'en-forms-bar' ? 'enform-progress-bar' : ''}`} style={{width: `calc(${percent}% + 20px)`}} />
</div>
}
{style === 'arc' &&
<svg className="progress-arc" xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 24 14">
<path className="background" d="M 2 12 A 1 1 0 1 1 22 12" />
<path className="foreground" d="M 2 12 A 1 1 0 1 1 22 12"
strokeDasharray={arcLength}
strokeDashoffset={`${(1 - (percent / 100)) * arcLength}`} />
</svg>
}
{text &&
<div
className={`counter-text ${100 <= percent ? 'counter-text-goal_reached' : ''}`}
role="presentation"
dangerouslySetInnerHTML={{__html: this.getCounterText()}}
/>
}
</div>
</section>
);
}
}
Loading

0 comments on commit 70d54b9

Please sign in to comment.