Initial Commit

This commit is contained in:
webstar1987923 2020-04-13 12:22:44 +01:00
commit 4fb452de3b
17 changed files with 39131 additions and 0 deletions

4
.babelrc Normal file
View File

@ -0,0 +1,4 @@
{
"presets": ["env"],
"plugins": ["babel-plugin-add-module-exports", "transform-object-rest-spread"]
}

12
.editorconfig Normal file
View 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
View 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
View 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
View 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

File diff suppressed because it is too large Load Diff

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

File diff suppressed because one or more lines are too long

6941
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

29
package.json Normal file
View 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
View 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
View 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
View 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
View 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);
})
)
);

File diff suppressed because one or more lines are too long

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
View 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
};