Wizard - Color Scheme Step

This commit is contained in:
Robin Ward
2016-09-22 09:52:19 -04:00
parent 9f12b571ef
commit 3f6e3b9aff
21 changed files with 343 additions and 23 deletions
@@ -0,0 +1,199 @@
/*eslint no-bitwise:0 */
import { observes } from 'ember-addons/ember-computed-decorators';
const WIDTH = 400;
const HEIGHT = 220;
const LINE_HEIGHT = 12.0;
const LOREM = `
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
Nullam eget sem non elit tincidunt rhoncus. Fusce velit nisl,
porttitor sed nisl ac, consectetur interdum metus. Fusce in
consequat augue, vel facilisis felis. Nunc tellus elit, and
semper vitae orci nec, blandit pharetra enim. Aenean a ebus
posuere nunc. Maecenas ultrices viverra enim ac commodo
Vestibulum nec quam sit amet libero ultricies sollicitudin.
Nulla quis scelerisque sem, eget volutpat velit. Fusce eget
accumsan sapien, nec feugiat quam. Quisque non risus.
placerat lacus vitae, lacinia nisi. Sed metus arcu, iaculis
sit amet cursus nec, sodales at eros.`;
function loadImage(src) {
const img = new Image();
img.src = src;
return new Ember.RSVP.Promise(resolve => img.onload = () => resolve(img));
};
function parseColor(color) {
const m = color.match(/^#([0-9a-f]{6})$/i);
if (m) {
const c = m[1];
return [ parseInt(c.substr(0,2),16), parseInt(c.substr(2,2),16), parseInt(c.substr(4,2),16) ];
}
return [0, 0, 0];
}
function brightness(color) {
return (color[0] * 0.299) + (color[1] * 0.587) + (color[2] * 0.114);
}
function lighten(color, percent) {
return '#' +
((0|(1<<8) + color[0] + (256 - color[0]) * percent / 100).toString(16)).substr(1) +
((0|(1<<8) + color[1] + (256 - color[1]) * percent / 100).toString(16)).substr(1) +
((0|(1<<8) + color[2] + (256 - color[2]) * percent / 100).toString(16)).substr(1);
}
function chooseBrighter(primary, secondary) {
const primaryCol = parseColor(primary);
const secondaryCol = parseColor(secondary);
return brightness(primaryCol) < brightness(secondaryCol) ? secondary : primary;
}
function darkLightDiff(adjusted, comparison, lightness, darkness) {
const adjustedCol = parseColor(adjusted);
const comparisonCol = parseColor(comparison);
return lighten(adjustedCol, (brightness(adjustedCol) < brightness(comparisonCol)) ?
lightness : darkness);
}
export default Ember.Component.extend({
ctx: null,
width: WIDTH,
height: HEIGHT,
loaded: false,
logo: null,
colorScheme: Ember.computed.alias('step.fieldsById.color_scheme.value'),
didInsertElement() {
this._super();
const c = this.$('canvas')[0];
this.ctx = c.getContext("2d");
Ember.RSVP.Promise.all([loadImage('/images/wizard/discourse-small.png'),
loadImage('/images/wizard/trout.png')]).then(result => {
this.logo = result[0];
this.avatar = result[1];
this.loaded = true;
this.triggerRepaint();
});
},
@observes('colorScheme')
triggerRepaint() {
Ember.run.scheduleOnce('afterRender', this, 'repaint');
},
repaint() {
if (!this.loaded) { return; }
const { ctx } = this;
const headerHeight = HEIGHT * 0.15;
const colorScheme = this.get('colorScheme');
const options = this.get('step.fieldsById.color_scheme.options');
const option = options.findProperty('id', colorScheme);
if (!option) { return; }
const colors = option.data.colors;
if (!colors) { return; }
ctx.fillStyle = colors.secondary;
ctx.fillRect(0, 0, WIDTH, HEIGHT);
// Header area
ctx.save();
ctx.beginPath();
ctx.rect(0, 0, WIDTH, headerHeight);
ctx.fillStyle = colors.header_background;
ctx.shadowColor = "rgba(0, 0, 0, 0.25)";
ctx.shadowBlur = 2;
ctx.shadowOffsetX = 0;
ctx.shadowOffsetY = 2;
ctx.fill();
ctx.restore();
const margin = WIDTH * 0.02;
const avatarSize = HEIGHT * 0.1;
// Logo
const headerMargin = headerHeight * 0.2;
const logoHeight = headerHeight - (headerMargin * 2);
const logoWidth = (logoHeight / this.logo.height) * this.logo.width;
ctx.drawImage(this.logo, headerMargin, headerMargin, logoWidth, logoHeight);
// Top right menu
ctx.drawImage(this.avatar, WIDTH - avatarSize - headerMargin, headerMargin, avatarSize, avatarSize);
ctx.fillStyle = darkLightDiff(colors.primary, colors.secondary, 45, 55);
ctx.font = "0.75em FontAwesome";
ctx.fillText("\uf0c9", WIDTH - (avatarSize * 2) - (headerMargin * 0.5), avatarSize);
ctx.fillText("\uf002", WIDTH - (avatarSize * 3) - (headerMargin * 0.5), avatarSize);
// Draw a fake topic
ctx.drawImage(this.avatar, margin, headerHeight + (HEIGHT * 0.17), avatarSize, avatarSize);
ctx.beginPath();
ctx.fillStyle = colors.primary;
ctx.font = "bold 0.75em 'Arial'";
ctx.fillText("Welcome to Discourse", margin, (HEIGHT * 0.25));
ctx.font = "0.5em 'Arial'";
let line = 0;
const lines = LOREM.split("\n");
for (let i=0; i<10; i++) {
line = (HEIGHT * 0.3) + (i * LINE_HEIGHT);
ctx.fillText(lines[i], margin + avatarSize + margin, line);
}
// Reply Button
ctx.beginPath();
ctx.rect(WIDTH * 0.57, line + LINE_HEIGHT, WIDTH * 0.1, HEIGHT * 0.07);
ctx.fillStyle = colors.tertiary;
ctx.fill();
ctx.fillStyle = chooseBrighter(colors.primary, colors.secondary);
ctx.font = "8px 'Arial'";
ctx.fillText("Reply", WIDTH * 0.595, line + (LINE_HEIGHT * 1.8));
// Icons
ctx.font = "0.5em FontAwesome";
ctx.fillStyle = colors.love;
ctx.fillText("\uf004", WIDTH * 0.48, line + (LINE_HEIGHT * 1.8));
ctx.fillStyle = darkLightDiff(colors.primary, colors.secondary, 65, 55);
ctx.fillText("\uf040", WIDTH * 0.525, line + (LINE_HEIGHT * 1.8));
// Draw Timeline
const timelineX = WIDTH * 0.8;
ctx.beginPath();
ctx.strokeStyle = colors.tertiary;
ctx.lineWidth = 0.5;
ctx.moveTo(timelineX, HEIGHT * 0.3);
ctx.lineTo(timelineX, HEIGHT * 0.6);
ctx.stroke();
// Timeline
ctx.beginPath();
ctx.strokeStyle = colors.tertiary;
ctx.lineWidth = 2;
ctx.moveTo(timelineX, HEIGHT * 0.3);
ctx.lineTo(timelineX, HEIGHT * 0.4);
ctx.stroke();
ctx.font = "Bold 0.5em Arial";
ctx.fillStyle = colors.primary;
ctx.fillText("1 / 20", timelineX + margin, (HEIGHT * 0.3) + (margin * 1.5));
// draw border
ctx.beginPath();
ctx.strokeStyle='rgba(0, 0, 0, 0.2)';
ctx.rect(0, 0, WIDTH, HEIGHT);
ctx.stroke();
}
});
@@ -6,6 +6,9 @@ export default Ember.Component.extend({
@computed('field.id')
inputClassName: id => `field-${Ember.String.dasherize(id)}`,
@computed('field.type')
inputComponentName: type => `wizard-field-${type}`
@computed('field.type', 'field.id')
inputComponentName(type, id) {
return (type === 'component') ? Ember.String.dasherize(id) : `wizard-field-${type}`;
}
});
@@ -8,6 +8,13 @@ export default Ember.Object.extend(ValidState, {
@computed('index')
displayIndex: index => index + 1,
@computed('fields.[]')
fieldsById(fields) {
const lookup = {};
fields.forEach(field => lookup[field.get('id')] = field);
return lookup;
},
checkFields() {
let allValid = true;
this.get('fields').forEach(field => {
@@ -0,0 +1,4 @@
<div class='preview-area'>
<canvas width={{width}} height={{height}}>
</canvas>
</div>
@@ -1 +1 @@
{{combo-box value=field.value content=field.options nameProperty="label" width="400px"}}
{{combo-box class=inputClassName value=field.value content=field.options nameProperty="label" width="400px"}}
@@ -2,7 +2,7 @@
<span class='label-value'>{{field.label}}</span>
<div class='input-area'>
{{component inputComponentName field=field inputClassName=inputClassName}}
{{component inputComponentName field=field step=step inputClassName=inputClassName}}
</div>
{{#if field.errorDescription}}
@@ -8,7 +8,7 @@
{{#wizard-step-form step=step}}
{{#each step.fields as |field|}}
{{wizard-field field=field}}
{{wizard-field field=field step=step}}
{{/each}}
{{/wizard-step-form}}
@@ -8,7 +8,7 @@ test("Wizard starts", assert => {
});
});
test("Forum Name Step", assert => {
test("Going back and forth in steps", assert => {
visit("/step/hello-world");
andThen(() => {
assert.ok(exists('.wizard-step'));
@@ -44,7 +44,10 @@ test("Forum Name Step", assert => {
assert.ok(!exists('.wizard-field .field-error-description'));
assert.ok(!exists('.wizard-step-title'));
assert.ok(!exists('.wizard-step-description'));
assert.ok(exists('input.field-email'), "went to the next step");
assert.ok(exists('select.field-snack'), "went to the next step");
assert.ok(exists('.preview-area'), "renders the component field");
assert.ok(!exists('.wizard-btn.next'));
assert.ok(exists('.wizard-btn.done'), 'last step shows a done button');
assert.ok(exists('.wizard-btn.back'), 'shows the back button');
@@ -10,6 +10,7 @@
//= require ember-qunit
//= require ember-shim
//= require wizard-application
//= require wizard-vendor
//= require helpers/assertions
//= require_tree ./acceptance
//= require_tree ./models
@@ -49,7 +49,10 @@ export default function() {
{
id: 'second-step',
index: 1,
fields: [{ id: 'email', type: 'text', required: true }],
fields: [
{ id: 'snack', type: 'dropdown', required: true },
{ id: 'scheme-preview', type: 'component' }
],
previous: 'hello-world'
}]
}