Add slider input for watermark opacity to theme editor
refs CNVS-22338, CNVS-23413, CNVS-23412 Test plan: * In Theme Editor, upload a water mark image * Set the opacity to something other than 50% * Preview the change and verify the opacity set on the bg image * Apply the change and verify the opacity on the bg image * Make sure the watermark image looks ok on as many canvas pages as possible * Reset to the canvas default theme and verify that the image is removed and the opacity is reset to 50% Change-Id: Ib6256343d20f9550f3d46c0a55731b9448c8a49c Reviewed-on: https://gerrit.instructure.com/64471 Reviewed-by: Chris Hart <chart@instructure.com> Tested-by: Jenkins Product-Review: Chris Hart <chart@instructure.com> QA-Review: Nathan Rogowski <nathan@instructure.com> QA-Review: Myller de Araujo <myller@instructure.com>
This commit is contained in:
parent
16b49a9d43
commit
82399ff885
|
@ -13,5 +13,5 @@ These modules are potentially shared but only used in one feature. If
|
|||
you can use one of them in your feature, please move it into this
|
||||
directory and update the require paths in the other feature.
|
||||
|
||||
- path/to/file.js
|
||||
- app/jsx/theme_editor/RangeInput.jsx
|
||||
|
||||
|
|
|
@ -26,10 +26,15 @@ define([
|
|||
types.image = React.PropTypes.shape(_.extend({
|
||||
type: React.PropTypes.oneOf(['image']).isRequired,
|
||||
accept: React.PropTypes.string.isRequired,
|
||||
helper_text: React.PropTypes.string.isRequired
|
||||
helper_text: React.PropTypes.string
|
||||
}, baseVarDef))
|
||||
|
||||
types.varDef = React.PropTypes.oneOfType([types.image, types.color])
|
||||
types.percentage = React.PropTypes.shape(_.extend({
|
||||
type: React.PropTypes.oneOf(['percentage']).isRequired,
|
||||
helper_text: React.PropTypes.string
|
||||
}, baseVarDef))
|
||||
|
||||
types.varDef = React.PropTypes.oneOfType([types.image, types.color, types.percentage])
|
||||
|
||||
types.brandConfig = React.PropTypes.shape({
|
||||
md5: types.md5,
|
||||
|
|
|
@ -0,0 +1,82 @@
|
|||
/** @jsx React.DOM */
|
||||
|
||||
define([
|
||||
'react',
|
||||
'jquery'
|
||||
], function (React, $) {
|
||||
|
||||
var RangeInput = React.createClass({
|
||||
propTypes: {
|
||||
min: React.PropTypes.number.isRequired,
|
||||
max: React.PropTypes.number.isRequired,
|
||||
defaultValue: React.PropTypes.number.isRequired,
|
||||
labelText: React.PropTypes.string.isRequired,
|
||||
name: React.PropTypes.string.isRequired,
|
||||
step: React.PropTypes.number,
|
||||
formatValue: React.PropTypes.func,
|
||||
onChange: React.PropTypes.func
|
||||
},
|
||||
|
||||
getDefaultProps: function() {
|
||||
return {
|
||||
step: 1,
|
||||
onChange: function(){},
|
||||
formatValue: val => val
|
||||
};
|
||||
},
|
||||
|
||||
getInitialState: function() {
|
||||
return { value: this.props.defaultValue };
|
||||
},
|
||||
|
||||
/* workaround for https://github.com/facebook/react/issues/554 */
|
||||
componentDidMount: function() {
|
||||
// https://connect.microsoft.com/IE/Feedback/Details/856998
|
||||
$(this.refs.rangeInput.getDOMNode()).on('input change', this.handleChange);
|
||||
},
|
||||
|
||||
componentWillUnmount: function() {
|
||||
$(this.refs.rangeInput.getDOMNode()).off('input change', this.handleChange);
|
||||
},
|
||||
/* end workaround */
|
||||
|
||||
handleChange: function(event) {
|
||||
this.setState({ value: event.target.value });
|
||||
this.props.onChange(event.target.value);
|
||||
},
|
||||
|
||||
render: function() {
|
||||
var {
|
||||
labelText,
|
||||
formatValue,
|
||||
onChange,
|
||||
value,
|
||||
...props
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<label className="RangeInput">
|
||||
<div className="RangeInput__label">
|
||||
{labelText}
|
||||
</div>
|
||||
<div className="RangeInput__control">
|
||||
<input className="RangeInput__input"
|
||||
ref="rangeInput"
|
||||
type="range"
|
||||
role="slider"
|
||||
aria-valuenow={this.props.defaultValue}
|
||||
aria-valuemin={this.props.min}
|
||||
aria-valuemax={this.props.max}
|
||||
aria-valuetext={formatValue(this.state.value)}
|
||||
onChange={function() {}}
|
||||
{...props} />
|
||||
<output htmlFor={this.props.name} className="RangeInput__value">
|
||||
{ formatValue(this.state.value) }
|
||||
</output>
|
||||
</div>
|
||||
</label>
|
||||
);
|
||||
}
|
||||
});
|
||||
return RangeInput;
|
||||
});
|
|
@ -2,12 +2,14 @@
|
|||
|
||||
define([
|
||||
'react',
|
||||
'i18n!theme_editor',
|
||||
'./ThemeEditorColorRow',
|
||||
'./ThemeEditorImageRow',
|
||||
'./RangeInput',
|
||||
'./PropTypes',
|
||||
'jquery',
|
||||
'jqueryui/accordion'
|
||||
], (React, ThemeEditorColorRow, ThemeEditorImageRow, customTypes, $) => {
|
||||
], (React, I18n, ThemeEditorColorRow, ThemeEditorImageRow, RangeInput, customTypes, $) => {
|
||||
|
||||
return React.createClass({
|
||||
|
||||
|
@ -44,8 +46,31 @@ define([
|
|||
onChange: this.props.changeSomething.bind(null, varDef.variable_name),
|
||||
placeholder: this.props.getDisplayValue(varDef.variable_name),
|
||||
varDef: varDef
|
||||
};
|
||||
|
||||
if (varDef.type === 'color') {
|
||||
return (
|
||||
<ThemeEditorColorRow {...props} />
|
||||
);
|
||||
} else if (varDef.type === 'image') {
|
||||
return (
|
||||
<ThemeEditorImageRow {...props} />
|
||||
);
|
||||
} else if (varDef.type === 'percentage') {
|
||||
var defaultValue = props.currentValue || props.placeholder;
|
||||
return (
|
||||
<RangeInput labelText={varDef.human_name}
|
||||
min={0}
|
||||
max={1}
|
||||
step={0.1}
|
||||
defaultValue={defaultValue ? parseFloat(defaultValue) : 0.5}
|
||||
name={'brand_config[variables][' + varDef.variable_name + ']'}
|
||||
onChange={value => props.onChange(value)}
|
||||
formatValue={value => I18n.toPercentage(value * 100, {precision: 0})} />
|
||||
);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
return varDef.type === 'color' ? ThemeEditorColorRow(props) : ThemeEditorImageRow(props)
|
||||
},
|
||||
|
||||
render() {
|
||||
|
|
|
@ -119,6 +119,7 @@ define([
|
|||
},
|
||||
|
||||
render() {
|
||||
var colorInputValue = (this.props.placeholder !== "none") ? this.props.placeholder : null;
|
||||
return (
|
||||
<section className="Theme__editor-accordion_element Theme__editor-color ic-Form-control">
|
||||
<div className="Theme__editor-form--color">
|
||||
|
@ -139,7 +140,7 @@ define([
|
|||
className="Theme__editor-color-block_input-sample Theme__editor-color-block_input"
|
||||
type="color"
|
||||
ref="colorpicker"
|
||||
value={this.props.placeholder}
|
||||
value={colorInputValue}
|
||||
role="presentation-only"
|
||||
onChange={event => this.inputChange(event.target.value) } />
|
||||
</label>
|
||||
|
|
|
@ -45,7 +45,17 @@ $ic-left-side-width: $ic-sp*15;
|
|||
|
||||
// Theme Editor watermark gets attached to this element as a background image
|
||||
body:not(.no-headers) & {
|
||||
background: $ic-brand-watermark left bottom no-repeat;
|
||||
z-index: 1;
|
||||
|
||||
&:before {
|
||||
content: "";
|
||||
bottom: 0; left: 0;
|
||||
width: 100%; height: 100%;
|
||||
position: absolute;
|
||||
z-index: -1;
|
||||
opacity: $ic-brand-watermark-opacity;
|
||||
background: transparent $ic-brand-watermark left bottom no-repeat;
|
||||
}
|
||||
}
|
||||
|
||||
body.ic-no-flex-layout & {
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
|
||||
[{
|
||||
"group_name": "Global Branding",
|
||||
"variables": [{
|
||||
|
@ -101,6 +102,12 @@
|
|||
"type": "image",
|
||||
"accept": "image/png,image/svg,image/gif,image/jpeg",
|
||||
"default": ""
|
||||
},{
|
||||
"human_name": "Watermark Opacity",
|
||||
"helper_text": "Specify the transparency of the watermark background image.",
|
||||
"variable_name": "ic-brand-watermark-opacity",
|
||||
"type": "percentage",
|
||||
"default": "1.0"
|
||||
},{
|
||||
"human_name": "Favicon",
|
||||
"helper_text": "You can use a single 16x16, 32x32, 48x48 ico file.",
|
||||
|
@ -155,6 +162,7 @@
|
|||
"human_name": "Background Image",
|
||||
"variable_name": "ic-brand-Login-body-bgd-image",
|
||||
"type": "image",
|
||||
"accept": "image/png,image/svg,image/gif,image/jpeg",
|
||||
"default": ""
|
||||
},
|
||||
{
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
@import "base/environment";
|
||||
@import "components/ic-range-input";
|
||||
|
||||
|
||||
//// Vertical content alignment on elements in Theme editor
|
||||
|
|
|
@ -0,0 +1,161 @@
|
|||
.RangeInput {
|
||||
$range-input-handle-color: $ic-brand-primary;
|
||||
$range-input-handle-color-hover: darken($ic-brand-primary, 10%);
|
||||
|
||||
$range-input-handle-border-color-focused: darken($ic-brand-primary, 50%);
|
||||
$range-input-handle-size: 20px;
|
||||
$range-input-handle-shadow: 0 3px 6px rgba(black, 0.3);
|
||||
|
||||
$range-input-slider-color: $ic-color-neutral;
|
||||
$range-input-value-bg-color: $ic-color-dark;
|
||||
$range-input-value-text-color: $ic-color-light;
|
||||
$range-input-label-color: $ic-color-dark;
|
||||
$range-input-label-font-size: $h2-font-size;
|
||||
|
||||
@mixin range-input-handle-selector() {
|
||||
&::-webkit-slider-thumb {
|
||||
@content;
|
||||
}
|
||||
&::-ms-thumb {
|
||||
@content;
|
||||
}
|
||||
&::-moz-range-thumb {
|
||||
@content;
|
||||
}
|
||||
}
|
||||
|
||||
@mixin range-input-track-selector() {
|
||||
&::-webkit-slider-runnable-track {
|
||||
@content;
|
||||
}
|
||||
&::-moz-range-track {
|
||||
@content;
|
||||
}
|
||||
&::-ms-track {
|
||||
@content;
|
||||
}
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
width: 100%;
|
||||
|
||||
// Style range input
|
||||
input[type=range] {
|
||||
-webkit-appearance: none;
|
||||
width: 100%; // for Firefox
|
||||
outline: none;
|
||||
margin: 0; padding: 0; // for IE
|
||||
|
||||
// Slider Handle/Thumb
|
||||
@include range-input-handle-selector {
|
||||
-webkit-appearance: none;
|
||||
width: $range-input-handle-size;
|
||||
height: $range-input-handle-size;
|
||||
border-radius: 50%;
|
||||
background: $range-input-handle-color;
|
||||
box-shadow: $range-input-handle-shadow;
|
||||
cursor: pointer;
|
||||
transition: all .15s ease-in-out;
|
||||
|
||||
&:hover {
|
||||
background: $range-input-handle-color-hover;
|
||||
}
|
||||
}
|
||||
|
||||
&::-webkit-slider-thumb {
|
||||
margin-top: -$range-input-handle-size/4;
|
||||
}
|
||||
|
||||
&:focus,
|
||||
&:active {
|
||||
outline: none;
|
||||
@include range-input-handle-selector {
|
||||
box-shadow: $range-input-handle-shadow,
|
||||
0 0 0 $range-input-handle-size/2 rgba($range-input-handle-color, .15);
|
||||
}
|
||||
}
|
||||
|
||||
// remove outline in FF
|
||||
&::-moz-focus-inner,
|
||||
&::-moz-focus-outer {
|
||||
border: 0;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
@include range-input-track-selector {
|
||||
height: $range-input-handle-size/2;
|
||||
border-radius: 5px;
|
||||
background: $range-input-slider-color;
|
||||
border-color: transparent;
|
||||
color: transparent;
|
||||
cursor: pointer;
|
||||
animate: 0.2s;
|
||||
}
|
||||
|
||||
&::-ms-track {
|
||||
background: transparent;
|
||||
border-width: $range-input-handle-size 0;
|
||||
}
|
||||
&::-ms-fill-upper,
|
||||
&::-ms-fill-lower {
|
||||
background: $range-input-slider-color;
|
||||
|
||||
border-radius: $range-input-handle-size/2;
|
||||
}
|
||||
|
||||
&::-ms-tooltip {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
// Label text
|
||||
.RangeInput__label {
|
||||
margin: 0 0 $ic-sp/2;
|
||||
display: block;
|
||||
line-height: 1.3;
|
||||
font-size: $range-input-label-font-size;
|
||||
color: $range-input-label-color;
|
||||
}
|
||||
|
||||
// Slider + Tooltip
|
||||
.RangeInput__control {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
// Slider
|
||||
.RangeInput__input {
|
||||
flex: auto;
|
||||
}
|
||||
|
||||
// Tooltip/Selected Value
|
||||
.RangeInput__value {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
width: 70px;
|
||||
color: $range-input-value-text-color;
|
||||
font-size: 16px;
|
||||
line-height: 20px;
|
||||
text-align: center;
|
||||
border-radius: 3px;
|
||||
background: $range-input-value-bg-color;
|
||||
padding: 5px 10px;
|
||||
margin-left: 7px;
|
||||
|
||||
&:after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 8px;
|
||||
left: -6px;
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-top: 7px solid transparent;
|
||||
border-right: 7px solid $range-input-value-bg-color;
|
||||
border-bottom: 7px solid transparent;
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue