mirror of
https://github.com/balena-io/etcher.git
synced 2025-07-24 11:46:31 +00:00
Implement SVGIcon Angular directive (#324)
* Inherit current scope in osOpenExternal directive This directive attempts to create a new isolated scope, which leads the errors when using this directive on top of another directive in the same element. Signed-off-by: Juan Cruz Viotti <jviottidc@gmail.com> * Implement SVGIcon Angular directive This directive replaces part of `hero-icon`, the old Polymer component. Signed-off-by: Juan Cruz Viotti <jviottidc@gmail.com>
This commit is contained in:
parent
04efd16ca3
commit
0dcc7b22b8
@ -15,7 +15,6 @@
|
||||
"tests"
|
||||
],
|
||||
"dependencies": {
|
||||
"polymer": "Polymer/polymer#^1.1.0",
|
||||
"angular-mixpanel": "~1.1.2"
|
||||
}
|
||||
}
|
||||
|
@ -4405,7 +4405,7 @@ a.badge:hover, a.badge:focus {
|
||||
height: auto;
|
||||
margin-left: auto;
|
||||
margin-right: auto; }
|
||||
.thumbnail .caption {
|
||||
.thumbnail .caption, .thumbnail .icon-caption {
|
||||
padding: 9px;
|
||||
color: white; }
|
||||
|
||||
@ -5681,7 +5681,7 @@ button.close {
|
||||
.clearfix:after {
|
||||
clear: both; }
|
||||
|
||||
.center-block {
|
||||
.center-block, .icon-caption {
|
||||
display: block;
|
||||
margin-left: auto;
|
||||
margin-right: auto; }
|
||||
@ -5997,7 +5997,7 @@ html {
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
.caption {
|
||||
.caption, .icon-caption {
|
||||
font-weight: bold;
|
||||
font-size: 11px;
|
||||
margin-bottom: 0;
|
||||
@ -6199,6 +6199,24 @@ button.btn:focus, button.progress-button:focus {
|
||||
100% {
|
||||
background-position: 20px 20px; } }
|
||||
|
||||
/*
|
||||
* Copyright 2016 Resin.io
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
.svg-icon[disabled] path {
|
||||
fill: #787c7f; }
|
||||
|
||||
/*
|
||||
* Copyright 2016 Resin.io
|
||||
*
|
||||
@ -6310,11 +6328,10 @@ button.btn:focus, button.progress-button:focus {
|
||||
.alert-ribbon--open {
|
||||
top: 0; }
|
||||
|
||||
hero-icon[disabled] .caption {
|
||||
color: #787c7f; }
|
||||
|
||||
hero-icon[disabled] path {
|
||||
fill: #787c7f; }
|
||||
.icon-caption {
|
||||
margin-top: 10px; }
|
||||
.icon-caption[disabled] {
|
||||
color: #787c7f; }
|
||||
|
||||
.block {
|
||||
display: block; }
|
||||
@ -6345,7 +6362,7 @@ body {
|
||||
border-top: 2px solid #64686a; }
|
||||
.section-footer .col-xs {
|
||||
padding: 0; }
|
||||
.section-footer hero-icon .icon {
|
||||
.section-footer .svg-icon {
|
||||
margin: 0 13px; }
|
||||
.section-footer [os-open-external]:hover {
|
||||
color: #85898c;
|
||||
|
@ -40,6 +40,7 @@ const app = angular.module('Etcher', [
|
||||
// Components
|
||||
require('./browser/components/progress-button/progress-button'),
|
||||
require('./browser/components/drive-selector/drive-selector'),
|
||||
require('./browser/components/svg-icon/svg-icon'),
|
||||
|
||||
// Pages
|
||||
require('./browser/pages/finish/finish'),
|
||||
|
63
lib/browser/components/svg-icon/directives/svg-icon.js
Normal file
63
lib/browser/components/svg-icon/directives/svg-icon.js
Normal file
@ -0,0 +1,63 @@
|
||||
/*
|
||||
* Copyright 2016 Resin.io
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
|
||||
/**
|
||||
* @summary SVGIcon directive
|
||||
* @function
|
||||
* @public
|
||||
*
|
||||
* @description
|
||||
* This directive provides an easy way to load SVG icons
|
||||
* by embedding the SVG contents inside the element, making
|
||||
* it possible to style icons with CSS.
|
||||
*
|
||||
* @example
|
||||
* <svg-icon path="path/to/icon.svg" width="40px" height="40px"></svg-icon>
|
||||
*/
|
||||
module.exports = function() {
|
||||
return {
|
||||
templateUrl: './browser/components/svg-icon/templates/svg-icon.tpl.html',
|
||||
replace: true,
|
||||
restrict: 'E',
|
||||
scope: {
|
||||
path: '@',
|
||||
width: '@',
|
||||
height: '@'
|
||||
},
|
||||
link: function(scope, element) {
|
||||
|
||||
// This means the path to the icon should be
|
||||
// relative to *this directory*.
|
||||
// TODO: There might be a way to compute the path
|
||||
// relatively to the `index.html`.
|
||||
const imagePath = path.join(__dirname, scope.path);
|
||||
|
||||
const contents = fs.readFileSync(imagePath, {
|
||||
encoding: 'utf8'
|
||||
});
|
||||
|
||||
element.html(contents);
|
||||
|
||||
element.css('width', scope.width || '40px');
|
||||
element.css('height', scope.height || '40px');
|
||||
}
|
||||
};
|
||||
};
|
19
lib/browser/components/svg-icon/styles/_svg-icon.scss
Normal file
19
lib/browser/components/svg-icon/styles/_svg-icon.scss
Normal file
@ -0,0 +1,19 @@
|
||||
/*
|
||||
* Copyright 2016 Resin.io
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
.svg-icon[disabled] path {
|
||||
fill: $color-disabled;
|
||||
}
|
28
lib/browser/components/svg-icon/svg-icon.js
Normal file
28
lib/browser/components/svg-icon/svg-icon.js
Normal file
@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Copyright 2016 Resin.io
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* @module Etcher.Components.SVGIcon
|
||||
*/
|
||||
|
||||
const angular = require('angular');
|
||||
const MODULE_NAME = 'Etcher.Components.SVGIcon';
|
||||
const SVGIcon = angular.module(MODULE_NAME, []);
|
||||
SVGIcon.directive('svgIcon', require('./directives/svg-icon'));
|
||||
|
||||
module.exports = MODULE_NAME;
|
@ -0,0 +1 @@
|
||||
<div class="svg-icon"></div>
|
@ -38,10 +38,8 @@ const nodeOpen = require('open');
|
||||
module.exports = function() {
|
||||
return {
|
||||
restrict: 'A',
|
||||
scope: {
|
||||
osOpenExternal: '@'
|
||||
},
|
||||
link: function(scope, element) {
|
||||
scope: false,
|
||||
link: function(scope, element, attributes) {
|
||||
|
||||
// This directive might be added to elements
|
||||
// other than buttons.
|
||||
@ -64,10 +62,10 @@ module.exports = function() {
|
||||
//
|
||||
// See https://github.com/electron/electron/issues/5039
|
||||
if (os.platform() === 'linux') {
|
||||
return nodeOpen(scope.osOpenExternal);
|
||||
return nodeOpen(attributes.osOpenExternal);
|
||||
}
|
||||
|
||||
shell.openExternal(scope.osOpenExternal);
|
||||
shell.openExternal(attributes.osOpenExternal);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
@ -1,57 +0,0 @@
|
||||
<link rel="import" href="../../bower_components/polymer/polymer.html">
|
||||
|
||||
<dom-module id="hero-icon">
|
||||
<template>
|
||||
<style>
|
||||
:host ::content .caption {
|
||||
display: block;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
:host ::content .icon {
|
||||
margin: 0 auto;
|
||||
}
|
||||
</style>
|
||||
<div class="icon"></div>
|
||||
|
||||
<template is="dom-if" if="{{label}}">
|
||||
<span class="caption">{{label}}</span>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
Polymer({
|
||||
is: 'hero-icon',
|
||||
properties: {
|
||||
path: {
|
||||
type: String
|
||||
},
|
||||
label: {
|
||||
type: String
|
||||
},
|
||||
width: {
|
||||
type: String
|
||||
},
|
||||
height: {
|
||||
type: String
|
||||
}
|
||||
},
|
||||
ready: function() {
|
||||
'use strict';
|
||||
|
||||
const iconElement = this.querySelector('.icon');
|
||||
const imagePath = path.join(__dirname, this.path);
|
||||
const contents = fs.readFileSync(imagePath, {
|
||||
encoding: 'utf8'
|
||||
});
|
||||
|
||||
iconElement.innerHTML = contents;
|
||||
iconElement.style.width = this.width || '40px';
|
||||
iconElement.style.height = this.height || '40px';
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</dom-module>
|
@ -7,8 +7,6 @@
|
||||
<link rel="stylesheet" type="text/css" href="css/desktop.css">
|
||||
<link rel="stylesheet" type="text/css" href="css/angular.css">
|
||||
|
||||
<link rel="import" href="components/hero-icon.html">
|
||||
|
||||
<script>
|
||||
window._trackJs = {
|
||||
token: '032448bc3d9e4cffb1e9b43d29d6c142',
|
||||
@ -37,19 +35,19 @@
|
||||
<main class="wrapper" ui-view></main>
|
||||
|
||||
<footer class="section-footer">
|
||||
<hero-icon path="../assets/images/etcher.svg"
|
||||
<svg-icon path="../../../../../assets/images/etcher.svg"
|
||||
width="83px"
|
||||
height="13px"
|
||||
os-open-external="https://etcher.io"></hero-icon>
|
||||
os-open-external="http://etcher.io"></svg-icon>
|
||||
|
||||
<span class="caption">
|
||||
IS <span os-open-external="https://github.com/resin-io/etcher">AN OPEN SOURCE PROJECT</span> BY
|
||||
</span>
|
||||
|
||||
<hero-icon path="../assets/images/resin.svg"
|
||||
<svg-icon path="../../../../../assets/images/resin.svg"
|
||||
width="79px"
|
||||
height="23px"
|
||||
os-open-external="https://resin.io"></hero-icon>
|
||||
os-open-external="https://resin.io"></svg-icon>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
|
@ -1,7 +1,8 @@
|
||||
<div class="row around-xs">
|
||||
<div class="col-xs">
|
||||
<div class="box text-center" os-dropzone="app.selectImage($file)">
|
||||
<hero-icon path="../assets/images/image.svg" label="SELECT IMAGE"></hero-icon>
|
||||
<svg-icon class="center-block" path="../../../../../assets/images/image.svg"></svg-icon>
|
||||
<span class="icon-caption">SELECT IMAGE</span>
|
||||
<span class="badge space-top-medium">1</span>
|
||||
|
||||
<div class="space-vertical-large">
|
||||
@ -25,7 +26,11 @@
|
||||
<div class="step-border-left" ng-disabled="!app.selection.hasImage()"></div>
|
||||
<div class="step-border-right" ng-disabled="!app.selection.hasImage() || !app.selection.hasDrive()"></div>
|
||||
|
||||
<hero-icon path="../assets/images/drive.svg" ng-disabled="!app.selection.hasImage()" label="SELECT DRIVE"></hero-icon>
|
||||
<svg-icon class="center-block"
|
||||
path="../../../../../assets/images/drive.svg"
|
||||
ng-disabled="!app.selection.hasImage()"></svg-icon>
|
||||
<span class="icon-caption"
|
||||
ng-disabled="!app.selection.hasImage()">SELECT DRIVE</span>
|
||||
|
||||
<span class="badge space-top-medium" ng-disabled="!app.selection.hasImage()">2</span>
|
||||
|
||||
@ -56,7 +61,12 @@
|
||||
|
||||
<div class="col-xs">
|
||||
<div class="box text-center">
|
||||
<hero-icon path="../assets/images/flash.svg" ng-disabled="!app.selection.hasImage() || !app.selection.hasDrive()" label="FLASH IMAGE"></hero-icon>
|
||||
<svg-icon class="center-block"
|
||||
path="../../../../../assets/images/flash.svg"
|
||||
ng-disabled="!app.selection.hasImage() || !app.selection.hasDrive()"></svg-icon>
|
||||
<span class="icon-caption"
|
||||
ng-disabled="!app.selection.hasImage() || !app.selection.hasDrive()">FLASH IMAGE</span>
|
||||
|
||||
<span class="badge space-top-medium" ng-disabled="!app.selection.hasImage() || !app.selection.hasDrive()">3</span>
|
||||
|
||||
<div class="space-vertical-large">
|
||||
|
@ -42,15 +42,19 @@ $alert-padding: 13px;
|
||||
@import "./components/button";
|
||||
@import "./components/tick";
|
||||
@import "../browser/components/progress-button/styles/progress-button";
|
||||
@import "../browser/components/svg-icon/styles/svg-icon";
|
||||
@import "./components/modal";
|
||||
@import "./components/alert-ribbon";
|
||||
|
||||
hero-icon[disabled] .caption {
|
||||
color: $color-disabled;
|
||||
}
|
||||
.icon-caption {
|
||||
@extend .caption;
|
||||
@extend .center-block;
|
||||
|
||||
hero-icon[disabled] path {
|
||||
fill: $color-disabled;
|
||||
margin-top: 10px;
|
||||
|
||||
&[disabled] {
|
||||
color: $color-disabled;
|
||||
}
|
||||
}
|
||||
|
||||
.block {
|
||||
@ -95,7 +99,7 @@ body {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
hero-icon .icon {
|
||||
.svg-icon {
|
||||
margin: 0 13px;
|
||||
}
|
||||
|
||||
|
76
tests/browser/components/svg-icon.spec.js
Normal file
76
tests/browser/components/svg-icon.spec.js
Normal file
@ -0,0 +1,76 @@
|
||||
'use strict';
|
||||
|
||||
const m = require('mochainon');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const angular = require('angular');
|
||||
require('angular-mocks');
|
||||
|
||||
describe('Browser: SVGIcon', function() {
|
||||
|
||||
beforeEach(angular.mock.module(
|
||||
require('../../../lib/browser/components/svg-icon/svg-icon')
|
||||
));
|
||||
|
||||
describe('svgIcon', function() {
|
||||
|
||||
let $compile;
|
||||
let $rootScope;
|
||||
|
||||
beforeEach(angular.mock.inject(function(_$compile_, _$rootScope_, $templateCache) {
|
||||
$compile = _$compile_;
|
||||
$rootScope = _$rootScope_;
|
||||
|
||||
// Workaround `Unexpected request: GET template.html. No more request expected` error.
|
||||
// See http://stackoverflow.com/a/29437480/1641422
|
||||
const templatePath = './browser/components/svg-icon/templates/svg-icon.tpl.html';
|
||||
const template = fs.readFileSync(path.resolve('lib', templatePath), {
|
||||
encoding: 'utf8'
|
||||
});
|
||||
$templateCache.put(templatePath, template);
|
||||
|
||||
}));
|
||||
|
||||
it('should inline the svg contents in the element', function() {
|
||||
const icon = "../../../../../assets/images/etcher.svg";
|
||||
let iconContents = fs.readFileSync(path.join(__dirname, '../../../assets/images/etcher.svg'), {
|
||||
encoding: 'utf8'
|
||||
}).split('\n');
|
||||
|
||||
// Injecting XML as HTML causes the XML header to be commented out.
|
||||
// Modify here to ease assertions later on.
|
||||
iconContents[0] = '<!--' + iconContents[0].slice(1, iconContents[0].length - 1) + '-->';
|
||||
iconContents = iconContents.join('\n');
|
||||
|
||||
const element = $compile(`<svg-icon path="${icon}">Resin.io</svg-icon>`)($rootScope);
|
||||
$rootScope.$digest();
|
||||
m.chai.expect(element.html()).to.equal(iconContents);
|
||||
});
|
||||
|
||||
it('should default the size to 40x40 pixels', function() {
|
||||
const icon = "../../../../../assets/images/etcher.svg";
|
||||
const element = $compile(`<svg-icon path="${icon}">Resin.io</svg-icon>`)($rootScope);
|
||||
$rootScope.$digest();
|
||||
m.chai.expect(element.css('width')).to.equal('40px');
|
||||
m.chai.expect(element.css('height')).to.equal('40px');
|
||||
});
|
||||
|
||||
it('should be able to set a custom height', function() {
|
||||
const icon = "../../../../../assets/images/etcher.svg";
|
||||
const element = $compile(`<svg-icon path="${icon}" width="20px">Resin.io</svg-icon>`)($rootScope);
|
||||
$rootScope.$digest();
|
||||
m.chai.expect(element.css('width')).to.equal('20px');
|
||||
m.chai.expect(element.css('height')).to.equal('40px');
|
||||
});
|
||||
|
||||
it('should be able to set a custom height', function() {
|
||||
const icon = "../../../../../assets/images/etcher.svg";
|
||||
const element = $compile(`<svg-icon path="${icon}" height="20px">Resin.io</svg-icon>`)($rootScope);
|
||||
$rootScope.$digest();
|
||||
m.chai.expect(element.css('width')).to.equal('40px');
|
||||
m.chai.expect(element.css('height')).to.equal('20px');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
Loading…
x
Reference in New Issue
Block a user