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