Initial Commit
This commit is contained in:
commit
4fb452de3b
4
.babelrc
Normal file
4
.babelrc
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"presets": ["env"],
|
||||||
|
"plugins": ["babel-plugin-add-module-exports", "transform-object-rest-spread"]
|
||||||
|
}
|
12
.editorconfig
Normal file
12
.editorconfig
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
root = true
|
||||||
|
|
||||||
|
[*]
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 4
|
||||||
|
end_of_line = LF
|
||||||
|
charset = utf-8
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
insert_final_newline = true
|
||||||
|
|
||||||
|
[*.md]
|
||||||
|
trim_trailing_whitespace = false
|
34
.eslintrc.js
Normal file
34
.eslintrc.js
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
module.exports = {
|
||||||
|
"env": {
|
||||||
|
"browser": true,
|
||||||
|
"node": true,
|
||||||
|
"es6": true
|
||||||
|
},
|
||||||
|
"extends": "eslint:recommended",
|
||||||
|
"parser": "babel-eslint",
|
||||||
|
"parserOptions": {
|
||||||
|
"sourceType": "module"
|
||||||
|
},
|
||||||
|
"rules": {
|
||||||
|
"indent": [
|
||||||
|
"error",
|
||||||
|
4
|
||||||
|
],
|
||||||
|
"linebreak-style": [
|
||||||
|
"error",
|
||||||
|
"unix"
|
||||||
|
],
|
||||||
|
"object-curly-spacing": [
|
||||||
|
"error",
|
||||||
|
"always"
|
||||||
|
],
|
||||||
|
"quotes": [
|
||||||
|
"error",
|
||||||
|
"single"
|
||||||
|
],
|
||||||
|
"semi": [
|
||||||
|
"error",
|
||||||
|
"always"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
};
|
33
.gitignore
vendored
Normal file
33
.gitignore
vendored
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
|
||||||
|
# Runtime data
|
||||||
|
pids
|
||||||
|
*.pid
|
||||||
|
*.seed
|
||||||
|
*.pid.lock
|
||||||
|
|
||||||
|
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||||
|
build/Release
|
||||||
|
|
||||||
|
# Dependency directories
|
||||||
|
node_modules/
|
||||||
|
jspm_packages/
|
||||||
|
|
||||||
|
# Optional npm cache directory
|
||||||
|
.npm
|
||||||
|
|
||||||
|
# Optional eslint cache
|
||||||
|
.eslintcache
|
||||||
|
|
||||||
|
# Optional REPL history
|
||||||
|
.node_repl_history
|
||||||
|
|
||||||
|
# Output of 'npm pack'
|
||||||
|
*.tgz
|
||||||
|
|
||||||
|
# Webstorm
|
||||||
|
.idea/
|
||||||
|
|
33
README.md
Normal file
33
README.md
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
# rs-ui-utils
|
||||||
|
Webpack based theme export utility library. Designed to be used both in FAM extension and the client app.
|
||||||
|
|
||||||
|
## !Important
|
||||||
|
Keep in mind, that whenever new build is made, it is necessary to:
|
||||||
|
* Bump the version number
|
||||||
|
* Reinstall this dependency on all the projects that use it (currently client and rs-utilities/famextension)
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
* Node v8.3.0 or higher
|
||||||
|
|
||||||
|
## Features
|
||||||
|
* Webpack 3 based
|
||||||
|
* ES6 source
|
||||||
|
* Exports util functions
|
||||||
|
* Assigns rsUiUtils prop to the window object
|
||||||
|
* ESLint used
|
||||||
|
|
||||||
|
## Setup
|
||||||
|
Install all the required dependencies
|
||||||
|
```bash
|
||||||
|
$ npm install
|
||||||
|
```
|
||||||
|
|
||||||
|
## Scripts
|
||||||
|
|
||||||
|
* `$ npm run build` - runs the tests and produces production version of rs-ui-utils under the `lib` folder
|
||||||
|
* `$ npm run dev` - produces development version of rs-ui-utils in a watch mode
|
||||||
|
* `$ npm run test` - runs the tests under the `test` folder
|
||||||
|
|
||||||
|
## Misc
|
||||||
|
* Inspired from *https://github.com/krasimir/webpack-library-starter*
|
||||||
|
|
13820
lib/rs-ui-utils.js
Normal file
13820
lib/rs-ui-utils.js
Normal file
File diff suppressed because it is too large
Load Diff
1
lib/rs-ui-utils.js.map
Normal file
1
lib/rs-ui-utils.js.map
Normal file
File diff suppressed because one or more lines are too long
1
lib/rs-ui-utils.min.js
vendored
Normal file
1
lib/rs-ui-utils.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
6941
package-lock.json
generated
Normal file
6941
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
29
package.json
Normal file
29
package.json
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
{
|
||||||
|
"name": "rs-ui-utils",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "",
|
||||||
|
"main": "lib/rs-ui-utils.js",
|
||||||
|
"scripts": {
|
||||||
|
"build": "webpack --env dev && webpack --env build && npm run test",
|
||||||
|
"dev": "webpack --progress --colors --watch --env dev",
|
||||||
|
"test": "mocha --require babel-core/register --colors ./test/*.js"
|
||||||
|
},
|
||||||
|
"author": "",
|
||||||
|
"license": "ISC",
|
||||||
|
"devDependencies": {
|
||||||
|
"babel-cli": "^6.26.0",
|
||||||
|
"babel-core": "^6.26.0",
|
||||||
|
"babel-eslint": "^8.2.2",
|
||||||
|
"babel-loader": "^7.1.3",
|
||||||
|
"babel-plugin-add-module-exports": "^0.2.1",
|
||||||
|
"babel-plugin-transform-object-rest-spread": "^6.26.0",
|
||||||
|
"babel-preset-env": "^1.6.1",
|
||||||
|
"chai": "^4.1.2",
|
||||||
|
"eslint": "^4.18.2",
|
||||||
|
"eslint-loader": "^2.0.0",
|
||||||
|
"mocha": "^5.0.2",
|
||||||
|
"ramda": "^0.25.0",
|
||||||
|
"webpack": "^3.10.0",
|
||||||
|
"yargs": "^11.0.0"
|
||||||
|
}
|
||||||
|
}
|
163
src/exportTheme.js
Normal file
163
src/exportTheme.js
Normal file
@ -0,0 +1,163 @@
|
|||||||
|
import {
|
||||||
|
getDesignerAddedFeatureName,
|
||||||
|
obtainUpdatedBackgroundAndDesignerFeatures
|
||||||
|
} from './util';
|
||||||
|
import { version } from '../package.json';
|
||||||
|
|
||||||
|
const exportTheme = (designData, themeName) => {
|
||||||
|
const { attributes, features, graphics, positions, previews } = designData.data;
|
||||||
|
|
||||||
|
let theme = Object.assign(
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
graphics,
|
||||||
|
features: features
|
||||||
|
.map((feature) => Object.assign(
|
||||||
|
{},
|
||||||
|
feature,
|
||||||
|
{ positionName: (positions.find(({ id }) => id === feature.position_id).name) }
|
||||||
|
)),
|
||||||
|
attributes,
|
||||||
|
previews
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const ids = function (arr, key) {
|
||||||
|
let retarr = [];
|
||||||
|
|
||||||
|
for (let i = 0; i < arr.length; i++) {
|
||||||
|
if (arr[i]) {
|
||||||
|
retarr.push(arr[i][key]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return retarr;
|
||||||
|
};
|
||||||
|
|
||||||
|
let phid;
|
||||||
|
|
||||||
|
for (let i = 0; i < theme.graphics.length; i++) {
|
||||||
|
if (theme.graphics[i].name === 'Placeholder Graphic') {
|
||||||
|
phid = theme.graphics[i].id;
|
||||||
|
delete theme.graphics[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const themeId = `theme_${designData.metadata.description}_${themeName}`.replace(/\W/g, '_').toLowerCase();
|
||||||
|
|
||||||
|
theme.themes = [
|
||||||
|
{
|
||||||
|
'id': themeId,
|
||||||
|
'design_url': designData.metadata.design_url,
|
||||||
|
'product_line_name': designData.metadata.product_line_name,
|
||||||
|
'target_name': designData.metadata.target_name,
|
||||||
|
'target_category_name': designData.metadata.target_category_name,
|
||||||
|
'manufacturer_name': designData.metadata.manufacturer_name,
|
||||||
|
'grouped_year': designData.metadata.grouped_year,
|
||||||
|
'rule_set_name': designData.metadata.rule_set_name,
|
||||||
|
'use_category_name': designData.metadata.use_category_name,
|
||||||
|
'use_path': designData.metadata.use_path ? designData.metadata.use_path : null,
|
||||||
|
'attribute_ids': ids(theme.attributes, 'link'),
|
||||||
|
'feature_ids': ids(theme.features, 'link'),
|
||||||
|
'graphic_ids': ids(theme.graphics, 'id')
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
for (let i = 0; i < theme.attributes.length; i++) {
|
||||||
|
delete theme.attributes[i].design_id;
|
||||||
|
delete theme.attributes[i].rule;
|
||||||
|
|
||||||
|
if (theme.attributes[i].value === phid) {
|
||||||
|
delete theme.attributes[i];
|
||||||
|
} else if (theme.attributes[i].name === 'Icon') {
|
||||||
|
theme.themes[0].graphic_ids.push(theme.attributes[i].value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = 0; i < theme.graphics.length; i++) {
|
||||||
|
if (theme.graphics[i]) {
|
||||||
|
if (theme.graphics[i].is_designer) {
|
||||||
|
delete theme.graphics[i].is_designer;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (theme.graphics[i].is_user_added) {
|
||||||
|
delete theme.graphics[i].is_user_added;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (theme.graphics[i].is_placeholder) {
|
||||||
|
delete theme.graphics[i].is_placeholder;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (theme.graphics[i].tags) {
|
||||||
|
delete theme.graphics[i].tags;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const getFeatureName = getDesignerAddedFeatureName(theme.features);
|
||||||
|
|
||||||
|
theme.features = obtainUpdatedBackgroundAndDesignerFeatures(
|
||||||
|
theme
|
||||||
|
.features
|
||||||
|
.map((feature) =>
|
||||||
|
Object.assign(
|
||||||
|
{},
|
||||||
|
feature,
|
||||||
|
{ name: getFeatureName(feature) }
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.map((feature) =>
|
||||||
|
Object
|
||||||
|
.keys(feature)
|
||||||
|
.reduce((acc, key) => {
|
||||||
|
if (key !== 'positionName') {
|
||||||
|
return Object.assign({}, acc, { [key]: feature[key] });
|
||||||
|
}
|
||||||
|
return acc;
|
||||||
|
}, {})
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
for (let i = 0; i < theme.features.length; i++) {
|
||||||
|
delete theme.features[i].design_id;
|
||||||
|
// delete theme.features[i].deleted;
|
||||||
|
|
||||||
|
if (theme.features[i].user_specific_information) {
|
||||||
|
for (let j = 0; j < theme.attributes.length; j++) {
|
||||||
|
if (theme.attributes[j] && theme.attributes[j].feature_id === theme.features[i].link) {
|
||||||
|
if (theme.attributes[j].name === 'Text' || theme.attributes[j].name === 'Icon') {
|
||||||
|
delete theme.attributes[j];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
theme.graphics = theme.graphics.filter(function (g) {
|
||||||
|
return g !== undefined;
|
||||||
|
});
|
||||||
|
theme.attributes = theme.attributes.filter(function (a) {
|
||||||
|
return a !== undefined;
|
||||||
|
});
|
||||||
|
|
||||||
|
theme.themes[0].graphic_ids = theme.themes[0].graphic_ids.filter(function (aid) {
|
||||||
|
for (let i = 0; i < theme.graphics.length; i++) {
|
||||||
|
if (theme.graphics[i].link === aid) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
|
theme.themes[0].attribute_ids = theme.themes[0].attribute_ids.filter(function (aid) {
|
||||||
|
for (let i = 0; i < theme.attributes.length; i++) {
|
||||||
|
if (theme.attributes[i].link === aid) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
|
return { theme, exporterVersion: version };
|
||||||
|
};
|
||||||
|
|
||||||
|
export default exportTheme;
|
6
src/index.js
Normal file
6
src/index.js
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
import exportTheme from './exportTheme';
|
||||||
|
import { downloadObjectAsFile } from './util';
|
||||||
|
|
||||||
|
export { exportTheme, downloadObjectAsFile };
|
||||||
|
|
||||||
|
window.rsUiUtils = { exportTheme, downloadObjectAsFile };
|
97
src/util.js
Normal file
97
src/util.js
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
import {
|
||||||
|
either,
|
||||||
|
anyPass,
|
||||||
|
curry,
|
||||||
|
__
|
||||||
|
} from 'ramda';
|
||||||
|
|
||||||
|
const isIcon = (type) => type === 'GraphicIcon';
|
||||||
|
|
||||||
|
const isText = (type) => type === 'Text';
|
||||||
|
|
||||||
|
const isCloned = (name) => name.indexOf('Cloned') !== -1;
|
||||||
|
|
||||||
|
const isClonedText = ({ type, name }) => isText(type) && isCloned(name);
|
||||||
|
|
||||||
|
const isClonedIcon = ({ type, name }) => isIcon(type) && isCloned(name);
|
||||||
|
|
||||||
|
const isUserAddedIcon = ({ name }) => name.match(/User Added Graphic/);
|
||||||
|
|
||||||
|
const isDesignerAddedIcon = ({ name }) => name.match(/Designer Added Icon [0-9]+/);
|
||||||
|
|
||||||
|
const isUserOrDesignerAddedIcon = (feature) => either(isUserAddedIcon, isDesignerAddedIcon)(feature);
|
||||||
|
|
||||||
|
const isUserAddedText = ({ name }) => name.match(/User Added Text/);
|
||||||
|
|
||||||
|
const isDesignerAddedText = ({ name }) => name.match(/Designer Added Text [0-9]+/);
|
||||||
|
|
||||||
|
const getFeatureName = (f, counter) => {
|
||||||
|
if (either(isUserOrDesignerAddedIcon, isClonedIcon)(f)) {
|
||||||
|
return `Designer Added Icon ${counter[f.positionName]['icon']++}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (anyPass([isUserAddedText, isDesignerAddedText, isClonedText])(f)) {
|
||||||
|
return `Designer Added Text ${counter[f.positionName]['text']++}`;
|
||||||
|
}
|
||||||
|
return f.name;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getDesignerAddedFeatureName = (features) => {
|
||||||
|
// keeps count of designer added features added to each position
|
||||||
|
// for naming purposes, e.g. Designer Added Icon #
|
||||||
|
const designerAddedCounter = features
|
||||||
|
.map(({ positionName }) => positionName)
|
||||||
|
.reduce((acc, positionName) =>
|
||||||
|
Object.assign({}, acc, { [positionName]: { icon: 1, text: 1 } }), {}
|
||||||
|
);
|
||||||
|
|
||||||
|
return curry(getFeatureName)(__, designerAddedCounter);
|
||||||
|
};
|
||||||
|
|
||||||
|
const isBackgroundFeature = ({ name }) => name === 'Background';
|
||||||
|
|
||||||
|
const isDesignerAddedFeature = ({ name }) => name.startsWith('Designer Added');
|
||||||
|
|
||||||
|
const obtainUpdatedBackgroundAndDesignerFeatures = (features) => {
|
||||||
|
const backgroundFeatures = features
|
||||||
|
.filter(isBackgroundFeature)
|
||||||
|
.map((feature) =>
|
||||||
|
Object.assign({}, feature, { linked: false, user_specific_information: true })
|
||||||
|
);
|
||||||
|
const designerAddedFeatures = features
|
||||||
|
.filter(isDesignerAddedFeature)
|
||||||
|
.map((feature) =>
|
||||||
|
Object.assign({}, feature, { linked: false, user_specific_information: false })
|
||||||
|
);
|
||||||
|
const remainingFeatures = features
|
||||||
|
.filter((feature) =>
|
||||||
|
!isBackgroundFeature(feature) && !(isDesignerAddedFeature(feature))
|
||||||
|
)
|
||||||
|
.map((feature) =>
|
||||||
|
Object.assign({}, feature, { linked: true, user_specific_information: true })
|
||||||
|
);
|
||||||
|
|
||||||
|
return backgroundFeatures.concat(designerAddedFeatures, remainingFeatures);
|
||||||
|
};
|
||||||
|
|
||||||
|
const downloadObjectAsFile = (JSObject, fileName) => {
|
||||||
|
// We will use a Blob here with objectURL as the data URL limit is 2MB, object URL limit is 500MB
|
||||||
|
const JSONString = JSON.stringify(JSObject, null, 2);
|
||||||
|
|
||||||
|
const themeFileBlob = new File([JSONString], fileName, { type: 'application/json' });
|
||||||
|
|
||||||
|
const themeObjectURL = URL.createObjectURL(themeFileBlob);
|
||||||
|
|
||||||
|
const themeSaveLink = document.createElementNS('http://www.w3.org/1999/xhtml', 'a');
|
||||||
|
|
||||||
|
themeSaveLink.href = themeObjectURL;
|
||||||
|
themeSaveLink.download = fileName;
|
||||||
|
|
||||||
|
themeSaveLink.dispatchEvent(new MouseEvent('click'));
|
||||||
|
};
|
||||||
|
|
||||||
|
export {
|
||||||
|
getDesignerAddedFeatureName,
|
||||||
|
obtainUpdatedBackgroundAndDesignerFeatures,
|
||||||
|
downloadObjectAsFile
|
||||||
|
};
|
16
test/exportTheme.js
Normal file
16
test/exportTheme.js
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
const { describe, it } = require('mocha');
|
||||||
|
const { expect } = require('chai');
|
||||||
|
const exportTheme = require('../src/exportTheme');
|
||||||
|
const ktm350Input = require('./mocks/ktm_350_input.json');
|
||||||
|
const ktm350Output = require('./mocks/ktm_350_output.json');
|
||||||
|
|
||||||
|
describe('Theme export', () =>
|
||||||
|
describe('#exportTheme()', () =>
|
||||||
|
it('Should export the theme correctly', () => {
|
||||||
|
const themeName = 'Test Theme';
|
||||||
|
const { theme: actual } = exportTheme(ktm350Input, themeName);
|
||||||
|
|
||||||
|
expect(actual).to.deep.equal(ktm350Output);
|
||||||
|
})
|
||||||
|
)
|
||||||
|
);
|
1
test/mocks/ktm_350_input.json
Normal file
1
test/mocks/ktm_350_input.json
Normal file
File diff suppressed because one or more lines are too long
17891
test/mocks/ktm_350_output.json
Normal file
17891
test/mocks/ktm_350_output.json
Normal file
File diff suppressed because one or more lines are too long
49
webpack.config.js
Normal file
49
webpack.config.js
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
/* global __dirname, require, module */
|
||||||
|
|
||||||
|
const webpack = require('webpack');
|
||||||
|
const UglifyJsPlugin = webpack.optimize.UglifyJsPlugin;
|
||||||
|
const path = require('path');
|
||||||
|
const env = require('yargs').argv.env;
|
||||||
|
const pkg = require('./package.json');
|
||||||
|
|
||||||
|
const libraryName = pkg.name;
|
||||||
|
|
||||||
|
let plugins = [], outputFile;
|
||||||
|
|
||||||
|
if (env === 'build') {
|
||||||
|
plugins.push(new UglifyJsPlugin({ minimize: true }));
|
||||||
|
outputFile = libraryName + '.min.js';
|
||||||
|
} else {
|
||||||
|
outputFile = libraryName + '.js';
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
entry: __dirname + '/src/index.js',
|
||||||
|
devtool: 'source-map',
|
||||||
|
output: {
|
||||||
|
path: __dirname + '/lib',
|
||||||
|
filename: outputFile,
|
||||||
|
library: libraryName,
|
||||||
|
libraryTarget: 'umd',
|
||||||
|
umdNamedDefine: true
|
||||||
|
},
|
||||||
|
module: {
|
||||||
|
rules: [
|
||||||
|
{
|
||||||
|
test: /(\.jsx|\.js)$/,
|
||||||
|
loader: 'babel-loader',
|
||||||
|
exclude: /(node_modules|bower_components)/
|
||||||
|
},
|
||||||
|
{
|
||||||
|
test: /(\.jsx|\.js)$/,
|
||||||
|
loader: 'eslint-loader',
|
||||||
|
exclude: /node_modules/
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
resolve: {
|
||||||
|
modules: [path.resolve('./node_modules'), path.resolve('./src')],
|
||||||
|
extensions: ['.json', '.js']
|
||||||
|
},
|
||||||
|
plugins
|
||||||
|
};
|
Loading…
x
Reference in New Issue
Block a user