Skip to content

Commit

Permalink
✨ [Story interactive] Basic Functionality for Detailed Results Compon…
Browse files Browse the repository at this point in the history
…ent (#35588)

* Pass selected option data through to component

* Add unit tests for detailed results component

* Restrict to either quiz or poll

* Update from merge

* Adjust unit tests

* Adjustments to logic for trigerring layout

* Refactor layout and element creation

* Update unit tests

* Make usePercentage into class private variable

* Minor refactors

* Modify result element type

* Add new interactive state entry type
  • Loading branch information
Brandons42 committed Aug 10, 2021
1 parent 3081038 commit ab5bf0e
Show file tree
Hide file tree
Showing 7 changed files with 447 additions and 35 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,24 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/

.i-amphtml-story-interactive-results-detailed {
position: relative !important;
height: 18em !important;
width: 100% !important;
}

.i-amphtml-story-interactive-results-image {
margin: auto !important;
margin-top: 4em !important;
border: 0.3em solid var(--interactive-accent-color) !important;
}

.i-amphtml-story-interactive-results-result {
border-radius: 50% !important;
background-position: center !important;
background-size: cover !important;
background-color: var(
--i-amphtml-interactive-placeholder-background
) !important;
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,19 +14,57 @@
* limitations under the License.
*/

import {InteractiveType} from './amp-story-interactive-abstract';
import {
AmpStoryInteractive,
InteractiveType,
} from './amp-story-interactive-abstract';
AmpStoryInteractiveResults,
decideStrategy,
} from './amp-story-interactive-results';
import {CSS} from '../../../build/amp-story-interactive-results-detailed-0.1.css';
import {htmlFor} from '#core/dom/static-template';
import {setImportantStyles} from '#core/dom/style';

export class AmpStoryInteractiveResultsDetailed extends AmpStoryInteractive {
/**
* @typedef {{
* element: !Element,
* answered: boolean
* }} ResultElementType
*/

/**
* Generates the template for the detailed results component.
*
* @param {!Element} element
* @return {!Element}
*/
const buildResultsDetailedTemplate = (element) => {
const html = htmlFor(element);
return html`
<div class="i-amphtml-story-interactive-results-container">
<div class="i-amphtml-story-interactive-results-prompt"></div>
<div class="i-amphtml-story-interactive-results-title"></div>
<div class="i-amphtml-story-interactive-results-detailed">
<div class="i-amphtml-story-interactive-results-image"></div>
</div>
<div class="i-amphtml-story-interactive-results-description"></div>
</div>
`;
};

export class AmpStoryInteractiveResultsDetailed extends AmpStoryInteractiveResults {
/**
* @param {!AmpElement} element
*/
constructor(element) {
super(element, InteractiveType.RESULTS, [2, 4]);
super(element);

/** @private {!Map<string, ResultElementType>} */
this.resultEls_ = {};

/** @private {?Element} */
this.resultsContainer_ = null;

/** @private {boolean} */
this.usePercentage_ = false;
}

/** @override */
Expand All @@ -36,7 +74,89 @@ export class AmpStoryInteractiveResultsDetailed extends AmpStoryInteractive {

/** @override */
buildComponent() {
this.rootEl_ = htmlFor(this.element)`<p>Detailed results component</p>`;
this.rootEl_ = buildResultsDetailedTemplate(this.element);
this.buildTop();
this.resultsContainer_ = this.rootEl_.querySelector(
'.i-amphtml-story-interactive-results-detailed'
);
this.usePercentage_ = decideStrategy(this.options_) === 'percentage';
return this.rootEl_;
}

/** @override */
onInteractiveReactStateUpdate(interactiveState) {
const components = Object.values(interactiveState);
let updateLayout = false;

components.forEach((e) => {
if (
(this.usePercentage_ && e.type === InteractiveType.QUIZ) ||
(!this.usePercentage_ && e.type === InteractiveType.POLL)
) {
if (!this.resultEls_[e.interactiveId]) {
updateLayout = true;
this.createResultEl_(e);
}
this.updateAnsweredResult_(e);
}
});

if (updateLayout) {
this.positionResultEls_();
}

super.onInteractiveReactStateUpdate(interactiveState);
}

/**
* Create and store an element that will show the results
* for an interactive component.
*
* @param {!./amp-story-interactive-results.InteractiveStateEntryType} e
* @private
*/
createResultEl_(e) {
const element = document.createElement('div');
element.classList.add('i-amphtml-story-interactive-results-result');
this.resultsContainer_.appendChild(element);
this.resultEls_[e.interactiveId] = {
element,
answered: false,
};
}

/**
* Sets the background image or text content for an answered result.
*
* @param {!./amp-story-interactive-results.InteractiveStateEntryType} e
* @private
*/
updateAnsweredResult_(e) {
if (!e.option || this.resultEls_[e.interactiveId].answered) {
return;
}

if (e.option.image) {
setImportantStyles(this.resultEls_[e.interactiveId].element, {
'background-image': 'url(' + e.option.image + ')',
});
} else {
this.resultEls_[e.interactiveId].element.textContent = e.option.text;
}
this.resultEls_[e.interactiveId].answered = true;
}

/**
* Sets (or resets) the positioning and sizing of each result.
*
* @private
*/
positionResultEls_() {
for (const id in this.resultEls_) {
setImportantStyles(this.resultEls_[id].element, {
'height': '5em',
'width': '5em',
});
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,15 @@ import {htmlFor} from '#core/dom/static-template';
export let InteractiveResultsDef;

/**
* Generates the template for the quiz.
* @typedef {{
* option: ?./amp-story-interactive-abstract.OptionConfigType,
* interactiveId: string
* }}
*/
export let InteractiveStateEntryType;

/**
* Generates the template for the results component.
*
* @param {!Element} element
* @return {!Element}
Expand All @@ -42,14 +50,6 @@ const buildResultsTemplate = (element) => {
const html = htmlFor(element);
return html`
<div class="i-amphtml-story-interactive-results-container">
<div class="i-amphtml-story-interactive-results-top">
<div class="i-amphtml-story-interactive-results-top-score">SCORE:</div>
<div class="i-amphtml-story-interactive-results-top-value">
<span class="i-amphtml-story-interactive-results-top-value-number"
>100</span
><span>%</span>
</div>
</div>
<div class="i-amphtml-story-interactive-results-image-border">
<div class="i-amphtml-story-interactive-results-image"></div>
</div>
Expand All @@ -60,6 +60,26 @@ const buildResultsTemplate = (element) => {
`;
};

/**
* Generates the template for the top bar of the results component.
*
* @param {!Element} element
* @return {!Element}
*/
const buildResultsTopTemplate = (element) => {
const html = htmlFor(element);
return html`
<div class="i-amphtml-story-interactive-results-top">
<div class="i-amphtml-story-interactive-results-top-score">SCORE:</div>
<div class="i-amphtml-story-interactive-results-top-value">
<span class="i-amphtml-story-interactive-results-top-value-number"
>100</span
><span>%</span>
</div>
</div>
`;
};

const HAS_IMAGE_CLASS = 'i-amphtml-story-interactive-has-image';
const HAS_SCORE_CLASS = 'i-amphtml-story-interactive-has-score';

Expand Down Expand Up @@ -186,16 +206,26 @@ export class AmpStoryInteractiveResults extends AmpStoryInteractive {
}

/** @override */
buildCallback() {
return super.buildCallback(CSS);
buildCallback(additionalCSS = '') {
return super.buildCallback(CSS + additionalCSS);
}

/** @override */
buildComponent() {
this.rootEl_ = buildResultsTemplate(this.element);
this.buildTop();
return this.rootEl_;
}

/**
* Inserts the HTML for the top bar into the rest of the results component.
*
* @protected
*/
buildTop() {
this.rootEl_.prepend(buildResultsTopTemplate(this.element));
}

/** @override */
layoutCallback() {
if (this.element.hasAttribute('prompt-text')) {
Expand All @@ -205,17 +235,17 @@ export class AmpStoryInteractiveResults extends AmpStoryInteractive {
}
this.storeService_.subscribe(
StateProperty.INTERACTIVE_REACT_STATE,
(data) => this.onInteractiveReactStateUpdate_(data),
(data) => this.onInteractiveReactStateUpdate(data),
true
);
}

/**
* Receives state updates and fills up DOM with the result
* @param {!Map<string, {option: ?./amp-story-interactive-abstract.OptionConfigType, interactiveId: string}>} interactiveState
* @private
* @param {!Map<string, InteractiveStateEntryType>} interactiveState
* @protected
*/
onInteractiveReactStateUpdate_(interactiveState) {
onInteractiveReactStateUpdate(interactiveState) {
const results = processResults(interactiveState, this.options_);
this.rootEl_.classList.toggle(HAS_SCORE_CLASS, results.percentage != null);
this.rootEl_.querySelector(
Expand Down
12 changes: 12 additions & 0 deletions extensions/amp-story-interactive/0.1/test/helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -205,3 +205,15 @@ export const populateQuiz = (
addConfigToInteractive(quiz, numOptions, correctOption);
quiz.element.setAttribute('id', 'TEST_quizId');
};

export const addThresholdsToInteractive = (interactive, thresholdList) => {
addConfigToInteractive(interactive, thresholdList.length, null, [
'results-category',
]);
thresholdList.forEach((threshold, index) => {
interactive.element.setAttribute(
`option-${index + 1}-results-threshold`,
threshold
);
});
};

0 comments on commit ab5bf0e

Please sign in to comment.