├── .babelrc
├── .github
└── FUNDING.yml
├── .gitignore
├── Icon
└── Icon.icns
├── Icon_Generator.provisionprofile
├── LICENSE.md
├── README.md
├── Screenshots
├── Icon.png
├── banner.png
├── gif.gif
├── s1.png
├── s2.png
├── s3.png
├── s4.png
└── s5.png
├── afterSignHook.js
├── appstore.sh
├── babel
├── afterSignHook.js
├── components
│ ├── Application.js
│ └── InputComponent.js
├── entitlements.plist
├── library
│ ├── Contents.json
│ └── Generator.js
├── main.js
└── renderer.js
├── entitlements.plist
├── index.html
├── package-lock.json
├── package.json
├── src
├── components
│ ├── Application.js
│ └── InputComponent.js
├── library
│ ├── Contents.json
│ └── Generator.js
├── main.js
└── renderer.js
└── yarn.lock
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "plugins": [
3 | "transform-react-jsx-source"
4 | ],
5 | "presets": ["env", "react"],
6 | "ignore": [
7 | "**/afterSignHook.js"
8 | ]
9 | }
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | github: onmyway133
4 | patreon: # Replace with a single Patreon username
5 | open_collective: # Replace with a single Open Collective username
6 | ko_fi: # Replace with a single Ko-fi username
7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
9 | liberapay: # Replace with a single Liberapay username
10 | issuehunt: # Replace with a single IssueHunt username
11 | otechie: # Replace with a single Otechie username
12 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
13 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | .tags*
3 | /.idea/
4 | /build/
5 | /dist/
6 | /external_binaries/
7 | /out/
8 | /vendor/download/
9 | /vendor/debian_jessie_amd64-sysroot/
10 | /vendor/debian_jessie_arm-sysroot/
11 | /vendor/debian_jessie_arm64-sysroot/
12 | /vendor/debian_jessie_i386-sysroot/
13 | /vendor/debian_wheezy_amd64-sysroot/
14 | /vendor/debian_wheezy_arm-sysroot/
15 | /vendor/debian_wheezy_i386-sysroot/
16 | /vendor/python_26/
17 | /vendor/npm/
18 | /vendor/llvm/
19 | /vendor/llvm-build/
20 | /vendor/.gclient
21 | node_modules/
22 | *.xcodeproj
23 | *.swp
24 | *.pyc
25 | *.VC.db
26 | *.VC.VC.opendb
27 | .vs/
28 | .vscode/
29 | *.vcxproj
30 | *.vcxproj.user
31 | *.vcxproj.filters
32 | *.sln
33 | *.log
34 | /brightray/brightray.opensdf
35 | /brightray/brightray.sdf
36 | /brightray/brightray.sln
37 | /brightray/brightray.vcxproj*
38 | /brightray/brightray.suo
39 | /brightray/brightray.v12.suo
40 | /brightray/brightray.xcodeproj/
41 |
42 | IconGenerator-mas-x64
43 | icon_generator-darwin-x64
44 | babel/
--------------------------------------------------------------------------------
/Icon/Icon.icns:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/onmyway133/IconGenerator/dd1ed6da60e68e01578ed2c6222b2e71e45fc585/Icon/Icon.icns
--------------------------------------------------------------------------------
/Icon_Generator.provisionprofile:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/onmyway133/IconGenerator/dd1ed6da60e68e01578ed2c6222b2e71e45fc585/Icon_Generator.provisionprofile
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | Licensed under the **MIT** license
2 |
3 | > Copyright (c) 2017 Khoa Pham
4 | >
5 | > Permission is hereby granted, free of charge, to any person obtaining
6 | > a copy of this software and associated documentation files (the
7 | > "Software"), to deal in the Software without restriction, including
8 | > without limitation the rights to use, copy, modify, merge, publish,
9 | > distribute, sublicense, and/or sell copies of the Software, and to
10 | > permit persons to whom the Software is furnished to do so, subject to
11 | > the following conditions:
12 | >
13 | > The above copyright notice and this permission notice shall be
14 | > included in all copies or substantial portions of the Software.
15 | >
16 | > THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 | > EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18 | > MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
19 | > IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
20 | > CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
21 | > TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
22 | > SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # 𝕴𝖈𝖔𝖓 𝕲𝖊𝖓𝖊𝖗𝖆𝖙𝖔𝖗
2 |
3 |
4 |
5 |
6 |
7 | Checkout https://indiegoodies.com/
8 |
9 |
10 |

11 |
12 |
13 | ## Description
14 |
15 | - An `electron.js` app used for generating app icons
16 | - Support macOS
17 | - Follow [Human Interface Guidelines iOS](https://developer.apple.com/ios/human-interface-guidelines/graphics/app-icon/)
18 | - Generate based on latest Xcode [Contents.json](https://developer.apple.com/library/content/documentation/Xcode/Reference/xcode_ref-Asset_Catalog_Format/Contents.html)
19 |
20 | ## How to install
21 |
22 | - Download latest release from https://github.com/onmyway133/IconGenerator/releases
23 |
24 | ## How to use
25 |
26 | - Drag image onto left box
27 | - Support `png`, `jpeg`, `jpg`, `webp`, `tiff`, `gif`, `svg`
28 | - Choose platform to generate: `iPhone`, `iPad`, `macOS`, `tvOS`, `watchOS`, or an `icns file`
29 | - Generated `AppIcon.appiconset` or `AppIcon.icns` and save to `Downloads` folder
30 |
31 |
32 |

33 |
34 |
35 | ## Credit
36 |
37 | - Icon http://emojione.com/
38 | - Use [sharp](https://github.com/lovell/sharp) to resize images
39 |
40 | ## Author
41 |
42 | Khoa Pham, onmyway133@gmail.com
43 |
44 | ## License
45 |
46 | **IconGenerator** is available under the MIT license. See the [LICENSE](https://github.com/onmyway133/IconGenerator/blob/master/LICENSE.md) file for more info.
47 |
--------------------------------------------------------------------------------
/Screenshots/Icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/onmyway133/IconGenerator/dd1ed6da60e68e01578ed2c6222b2e71e45fc585/Screenshots/Icon.png
--------------------------------------------------------------------------------
/Screenshots/banner.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/onmyway133/IconGenerator/dd1ed6da60e68e01578ed2c6222b2e71e45fc585/Screenshots/banner.png
--------------------------------------------------------------------------------
/Screenshots/gif.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/onmyway133/IconGenerator/dd1ed6da60e68e01578ed2c6222b2e71e45fc585/Screenshots/gif.gif
--------------------------------------------------------------------------------
/Screenshots/s1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/onmyway133/IconGenerator/dd1ed6da60e68e01578ed2c6222b2e71e45fc585/Screenshots/s1.png
--------------------------------------------------------------------------------
/Screenshots/s2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/onmyway133/IconGenerator/dd1ed6da60e68e01578ed2c6222b2e71e45fc585/Screenshots/s2.png
--------------------------------------------------------------------------------
/Screenshots/s3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/onmyway133/IconGenerator/dd1ed6da60e68e01578ed2c6222b2e71e45fc585/Screenshots/s3.png
--------------------------------------------------------------------------------
/Screenshots/s4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/onmyway133/IconGenerator/dd1ed6da60e68e01578ed2c6222b2e71e45fc585/Screenshots/s4.png
--------------------------------------------------------------------------------
/Screenshots/s5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/onmyway133/IconGenerator/dd1ed6da60e68e01578ed2c6222b2e71e45fc585/Screenshots/s5.png
--------------------------------------------------------------------------------
/afterSignHook.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | function _asyncToGenerator(fn) { return function () { var gen = fn.apply(this, arguments); return new Promise(function (resolve, reject) { function step(key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { return Promise.resolve(value).then(function (value) { step("next", value); }, function (err) { step("throw", err); }); } } return step("next"); }); }; }
4 |
5 | var fs = require('fs');
6 | var path = require('path');
7 | var electron_notarize = require('electron-notarize');
8 |
9 | module.exports = function () {
10 | var _ref = _asyncToGenerator( /*#__PURE__*/regeneratorRuntime.mark(function _callee(params) {
11 | var appId, appPath;
12 | return regeneratorRuntime.wrap(function _callee$(_context) {
13 | while (1) {
14 | switch (_context.prev = _context.next) {
15 | case 0:
16 | if (!(process.platform !== 'darwin')) {
17 | _context.next = 2;
18 | break;
19 | }
20 |
21 | return _context.abrupt('return');
22 |
23 | case 2:
24 | console.log('afterSign hook triggered', params);
25 |
26 | // Same appId in electron-builder.
27 | appId = 'com.onmyway133.IconGenerator';
28 | appPath = path.join(params.appOutDir, params.packager.appInfo.productFilename + '.app');
29 |
30 | if (fs.existsSync(appPath)) {
31 | _context.next = 7;
32 | break;
33 | }
34 |
35 | throw new Error('Cannot find application at: ' + appPath);
36 |
37 | case 7:
38 |
39 | console.log('Notarizing ' + appId + ' found at ' + appPath);
40 |
41 | _context.prev = 8;
42 | _context.next = 11;
43 | return electron_notarize.notarize({
44 | appBundleId: appId,
45 | appPath: appPath,
46 | appleId: process.env.appleId,
47 | appleIdPassword: process.env.appleIdPassword
48 | });
49 |
50 | case 11:
51 | _context.next = 16;
52 | break;
53 |
54 | case 13:
55 | _context.prev = 13;
56 | _context.t0 = _context['catch'](8);
57 |
58 | console.error(_context.t0);
59 |
60 | case 16:
61 |
62 | console.log('Done notarizing ' + appId);
63 |
64 | case 17:
65 | case 'end':
66 | return _context.stop();
67 | }
68 | }
69 | }, _callee, this, [[8, 13]]);
70 | }));
71 |
72 | return function (_x) {
73 | return _ref.apply(this, arguments);
74 | };
75 | }();
--------------------------------------------------------------------------------
/appstore.sh:
--------------------------------------------------------------------------------
1 | npx electron-packager . "IconGenerator" --app-bundle-id=com.onmyway133.IconGenerator --helper-bundle-id=com.onmyway133.IconGenerator.helper --app-version=1.4.0 --build-version=8 --platform=mas --arch=x64 --icon=Icon/Icon.icns --overwrite
2 | npx electron-osx-sign "IconGenerator-mas-x64/IconGenerator.app" --verbose
3 | npx electron-osx-flat "IconGenerator-mas-x64/IconGenerator.app" --verbose
4 |
--------------------------------------------------------------------------------
/babel/afterSignHook.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs');
2 | const path = require('path');
3 | var electron_notarize = require('electron-notarize');
4 |
5 | module.exports = async function (params) {
6 | // Only notarize the app on Mac OS only.
7 | if (process.platform !== 'darwin') {
8 | return;
9 | }
10 | console.log('afterSign hook triggered', params);
11 |
12 | // Same appId in electron-builder.
13 | let appId = 'com.onmyway133.IconGenerator';
14 |
15 | let appPath = path.join(params.appOutDir, `${params.packager.appInfo.productFilename}.app`);
16 | if (!fs.existsSync(appPath)) {
17 | throw new Error(`Cannot find application at: ${appPath}`);
18 | }
19 |
20 | console.log(`Notarizing ${appId} found at ${appPath}`);
21 |
22 | try {
23 | await electron_notarize.notarize({
24 | appBundleId: appId,
25 | appPath: appPath,
26 | appleId: process.env.appleId,
27 | appleIdPassword: process.env.appleIdPassword
28 | });
29 | } catch (error) {
30 | console.error(error);
31 | }
32 |
33 | console.log(`Done notarizing ${appId}`);
34 | };
--------------------------------------------------------------------------------
/babel/components/Application.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var _jsxFileName = 'src/components/Application.js';
4 |
5 | var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
6 |
7 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
8 |
9 | function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }
10 |
11 | function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
12 |
13 | var React = require('react');
14 | var ReactDOM = require('react-dom');
15 | var Paper = require('@material-ui/core/Paper');
16 | var MuiThemeProvider = require('material-ui/styles/').MuiThemeProvider;
17 | var InputComponent = require('./InputComponent.js');
18 |
19 | // http://www.material-ui.com/#/get-started/installation
20 | // injectTapEventPlugin()
21 |
22 | var Application = function (_React$Component) {
23 | _inherits(Application, _React$Component);
24 |
25 | function Application(props) {
26 | _classCallCheck(this, Application);
27 |
28 | return _possibleConstructorReturn(this, (Application.__proto__ || Object.getPrototypeOf(Application)).call(this, props));
29 | }
30 |
31 | _createClass(Application, [{
32 | key: 'render',
33 | value: function render() {
34 | var styles = {
35 | div: {
36 | width: '100%',
37 | alignSelf: 'stretch',
38 | display: 'flex',
39 | backgroundcolor: '#F8F8F0'
40 | }
41 | };
42 |
43 | return React.createElement(
44 | MuiThemeProvider,
45 | {
46 | __source: {
47 | fileName: _jsxFileName,
48 | lineNumber: 26
49 | }
50 | },
51 | React.createElement(
52 | 'div',
53 | { style: styles.div, __source: {
54 | fileName: _jsxFileName,
55 | lineNumber: 27
56 | }
57 | },
58 | React.createElement(InputComponent, { file: this.props.file, error: this.props.error, __source: {
59 | fileName: _jsxFileName,
60 | lineNumber: 28
61 | }
62 | })
63 | )
64 | );
65 | }
66 | }]);
67 |
68 | return Application;
69 | }(React.Component);
70 |
71 | module.exports = Application;
--------------------------------------------------------------------------------
/babel/components/InputComponent.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var _jsxFileName = 'src/components/InputComponent.js';
4 |
5 | var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
6 |
7 | var _react = require('react');
8 |
9 | var _react2 = _interopRequireDefault(_react);
10 |
11 | var _reactDom = require('react-dom');
12 |
13 | var _reactDom2 = _interopRequireDefault(_reactDom);
14 |
15 | var _Paper = require('@material-ui/core/Paper');
16 |
17 | var _Paper2 = _interopRequireDefault(_Paper);
18 |
19 | var _RadioGroup = require('@material-ui/core/RadioGroup');
20 |
21 | var _RadioGroup2 = _interopRequireDefault(_RadioGroup);
22 |
23 | var _Radio = require('@material-ui/core/Radio');
24 |
25 | var _Radio2 = _interopRequireDefault(_Radio);
26 |
27 | var _Button = require('@material-ui/core/Button');
28 |
29 | var _Button2 = _interopRequireDefault(_Button);
30 |
31 | var _FormControlLabel = require('@material-ui/core/FormControlLabel');
32 |
33 | var _FormControlLabel2 = _interopRequireDefault(_FormControlLabel);
34 |
35 | var _DialogContentText = require('@material-ui/core/DialogContentText');
36 |
37 | var _DialogContentText2 = _interopRequireDefault(_DialogContentText);
38 |
39 | var _imageSize = require('image-size');
40 |
41 | var _imageSize2 = _interopRequireDefault(_imageSize);
42 |
43 | var _Generator = require('../library/Generator');
44 |
45 | var _Generator2 = _interopRequireDefault(_Generator);
46 |
47 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
48 |
49 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
50 |
51 | function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }
52 |
53 | function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
54 |
55 | var InputComponent = function (_React$Component) {
56 | _inherits(InputComponent, _React$Component);
57 |
58 | function InputComponent(props) {
59 | _classCallCheck(this, InputComponent);
60 |
61 | var _this = _possibleConstructorReturn(this, (InputComponent.__proto__ || Object.getPrototypeOf(InputComponent)).call(this, props));
62 |
63 | _this.state = {
64 | choice: 'iOS (iPhone)'
65 | };
66 |
67 | _this.handleChoiceChange = _this.handleChoiceChange.bind(_this);
68 | _this.handleGenerate = _this.handleGenerate.bind(_this);
69 | return _this;
70 | }
71 |
72 | _createClass(InputComponent, [{
73 | key: 'render',
74 | value: function render() {
75 | var styles = {
76 | div: {
77 | display: 'flex',
78 | alignSelf: 'stretch',
79 | width: '100%'
80 | }
81 | };
82 |
83 | return _react2.default.createElement(
84 | 'div',
85 | { style: styles.div, __source: {
86 | fileName: _jsxFileName,
87 | lineNumber: 34
88 | }
89 | },
90 | this.makeImage(),
91 | this.makeChoices()
92 | );
93 | }
94 |
95 | // action
96 |
97 | }, {
98 | key: 'handleChoiceChange',
99 | value: function handleChoiceChange(event, value) {
100 | this.setState({
101 | choice: value
102 | });
103 | }
104 | }, {
105 | key: 'handleGenerate',
106 | value: function handleGenerate() {
107 | var generator = new _Generator2.default();
108 | generator.generate(this.props.file.path, this.state.choice);
109 | }
110 |
111 | // make
112 |
113 | }, {
114 | key: 'makeImage',
115 | value: function makeImage() {
116 | var divOptions = {
117 | style: {
118 | flex: 1.5,
119 | padding: '10px'
120 | }
121 | };
122 |
123 | var paperOptions = {
124 | style: {
125 | width: '100%',
126 | height: '100%'
127 | }
128 | };
129 |
130 | return _react2.default.createElement(
131 | 'div',
132 | { style: divOptions.style, __source: {
133 | fileName: _jsxFileName,
134 | lineNumber: 72
135 | }
136 | },
137 | _react2.default.createElement(
138 | _Paper2.default,
139 | { style: paperOptions.style, __source: {
140 | fileName: _jsxFileName,
141 | lineNumber: 73
142 | }
143 | },
144 | this.makeImageElement(),
145 | this.makeImageDescriptionElement()
146 | )
147 | );
148 | }
149 | }, {
150 | key: 'makeImageElement',
151 | value: function makeImageElement() {
152 | var path = void 0;
153 | if (this.props.file !== null) {
154 | path = this.props.file.path;
155 | } else {
156 | path = '';
157 | }
158 |
159 | var styles = {
160 | div: {
161 | display: 'flex',
162 | justifyContent: 'center',
163 | paddingTop: '20px'
164 | },
165 | image: {
166 | width: '300px',
167 | height: '300px',
168 | border: '1px solid black'
169 | }
170 | };
171 |
172 | return _react2.default.createElement(
173 | 'div',
174 | { style: styles.div, __source: {
175 | fileName: _jsxFileName,
176 | lineNumber: 103
177 | }
178 | },
179 | _react2.default.createElement('img', { style: styles.image, src: path, __source: {
180 | fileName: _jsxFileName,
181 | lineNumber: 104
182 | }
183 | })
184 | );
185 | }
186 | }, {
187 | key: 'makeImageDescriptionElement',
188 | value: function makeImageDescriptionElement() {
189 | var text = void 0;
190 | if (this.props.file !== null) {
191 | var size = (0, _imageSize2.default)(this.props.file.path);
192 | var sizeDescription = size.width + 'x' + size.height;
193 | text = this.props.file.name + ' (' + sizeDescription + ')';
194 | } else if (this.props.error !== null) {
195 | text = this.props.error;
196 | } else {
197 | text = 'Drag image onto the above box. Prefer 1024x1024 or larger';
198 | }
199 |
200 | var styles = {
201 | div: {
202 | display: 'flex',
203 | justifyContent: 'center',
204 | marginTop: '10px'
205 | },
206 | text: {
207 | textAlign: 'center'
208 | }
209 | };
210 |
211 | return _react2.default.createElement(
212 | 'div',
213 | { style: styles.div, __source: {
214 | fileName: _jsxFileName,
215 | lineNumber: 133
216 | }
217 | },
218 | _react2.default.createElement(
219 | _DialogContentText2.default,
220 | { style: styles.text, __source: {
221 | fileName: _jsxFileName,
222 | lineNumber: 134
223 | }
224 | },
225 | text
226 | )
227 | );
228 | }
229 | }, {
230 | key: 'makeChoices',
231 | value: function makeChoices() {
232 | var styles = {
233 | div: {
234 | flex: 1,
235 | padding: '10px'
236 | },
237 | paper: {
238 | paddingTop: '10px',
239 | paddingBottom: '10px'
240 | },
241 | group: {
242 | paddingLeft: '10px'
243 | }
244 | };
245 |
246 | var choices = ["iOS (iPhone)", "iOS (iPad)", "iOS (Universal)", "macOS", "macOS (Icns)", "watchOS"];
247 |
248 | var choiceElements = choices.map(function (name) {
249 | return _react2.default.createElement(_FormControlLabel2.default, { value: name, control: _react2.default.createElement(_Radio2.default, {
250 | __source: {
251 | fileName: _jsxFileName,
252 | lineNumber: 160
253 | }
254 | }), label: name, key: name, __source: {
255 | fileName: _jsxFileName,
256 | lineNumber: 160
257 | }
258 | });
259 | });
260 |
261 | return _react2.default.createElement(
262 | 'div',
263 | { style: styles.div, __source: {
264 | fileName: _jsxFileName,
265 | lineNumber: 165
266 | }
267 | },
268 | _react2.default.createElement(
269 | _Paper2.default,
270 | { style: styles.paper, __source: {
271 | fileName: _jsxFileName,
272 | lineNumber: 166
273 | }
274 | },
275 | _react2.default.createElement(_RadioGroup2.default, {
276 | style: styles.group,
277 | defaultselected: this.state.choice,
278 | onChange: this.handleChoiceChange,
279 | children: choiceElements, __source: {
280 | fileName: _jsxFileName,
281 | lineNumber: 167
282 | }
283 | }),
284 | this.makeGenerateButton()
285 | )
286 | );
287 | }
288 | }, {
289 | key: 'makeGenerateButton',
290 | value: function makeGenerateButton() {
291 | var styles = {
292 | div: {
293 | display: 'flex',
294 | justifyContent: 'center',
295 | marginTop: '10px'
296 | },
297 | button: {
298 | width: '80%'
299 | }
300 | };
301 |
302 | return _react2.default.createElement(
303 | 'div',
304 | { style: styles.div, __source: {
305 | fileName: _jsxFileName,
306 | lineNumber: 191
307 | }
308 | },
309 | _react2.default.createElement(
310 | _Button2.default,
311 | {
312 | style: styles.button,
313 | color: 'secondary',
314 | onClick: this.handleGenerate,
315 | disabled: this.props.file === undefined,
316 | variant: 'contained', __source: {
317 | fileName: _jsxFileName,
318 | lineNumber: 192
319 | }
320 | },
321 | 'Generate'
322 | )
323 | );
324 | }
325 | }]);
326 |
327 | return InputComponent;
328 | }(_react2.default.Component);
329 |
330 | module.exports = InputComponent;
--------------------------------------------------------------------------------
/babel/entitlements.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | com.apple.security.cs.allow-jit
6 |
7 | com.apple.security.cs.allow-unsigned-executable-memory
8 |
9 | com.apple.security.cs.allow-dyld-environment-variables
10 |
11 |
12 |
--------------------------------------------------------------------------------
/babel/library/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "iphone",
5 | "size" : "20x20",
6 | "scale" : "2x"
7 | },
8 | {
9 | "idiom" : "iphone",
10 | "size" : "20x20",
11 | "scale" : "3x"
12 | },
13 | {
14 | "idiom" : "iphone",
15 | "size" : "29x29",
16 | "scale" : "2x"
17 | },
18 | {
19 | "idiom" : "iphone",
20 | "size" : "29x29",
21 | "scale" : "3x"
22 | },
23 | {
24 | "idiom" : "iphone",
25 | "size" : "40x40",
26 | "scale" : "2x"
27 | },
28 | {
29 | "idiom" : "iphone",
30 | "size" : "40x40",
31 | "scale" : "3x"
32 | },
33 | {
34 | "idiom" : "iphone",
35 | "size" : "60x60",
36 | "scale" : "2x"
37 | },
38 | {
39 | "idiom" : "iphone",
40 | "size" : "60x60",
41 | "scale" : "3x"
42 | },
43 | {
44 | "idiom" : "ipad",
45 | "size" : "20x20",
46 | "scale" : "1x"
47 | },
48 | {
49 | "idiom" : "ipad",
50 | "size" : "20x20",
51 | "scale" : "2x"
52 | },
53 | {
54 | "idiom" : "ipad",
55 | "size" : "29x29",
56 | "scale" : "1x"
57 | },
58 | {
59 | "idiom" : "ipad",
60 | "size" : "29x29",
61 | "scale" : "2x"
62 | },
63 | {
64 | "idiom" : "ipad",
65 | "size" : "40x40",
66 | "scale" : "1x"
67 | },
68 | {
69 | "idiom" : "ipad",
70 | "size" : "40x40",
71 | "scale" : "2x"
72 | },
73 | {
74 | "idiom" : "ipad",
75 | "size" : "76x76",
76 | "scale" : "1x"
77 | },
78 | {
79 | "idiom" : "ipad",
80 | "size" : "76x76",
81 | "scale" : "2x"
82 | },
83 | {
84 | "idiom" : "ipad",
85 | "size" : "83.5x83.5",
86 | "scale" : "2x"
87 | },
88 | {
89 | "idiom" : "ios-marketing",
90 | "size" : "1024x1024",
91 | "scale" : "1x"
92 | },
93 | {
94 | "idiom" : "car",
95 | "size" : "60x60",
96 | "scale" : "2x"
97 | },
98 | {
99 | "idiom" : "car",
100 | "size" : "60x60",
101 | "scale" : "3x"
102 | },
103 | {
104 | "idiom" : "mac",
105 | "size" : "16x16",
106 | "scale" : "1x"
107 | },
108 | {
109 | "idiom" : "mac",
110 | "size" : "16x16",
111 | "scale" : "2x"
112 | },
113 | {
114 | "idiom" : "mac",
115 | "size" : "32x32",
116 | "scale" : "1x"
117 | },
118 | {
119 | "idiom" : "mac",
120 | "size" : "32x32",
121 | "scale" : "2x"
122 | },
123 | {
124 | "idiom" : "mac",
125 | "size" : "128x128",
126 | "scale" : "1x"
127 | },
128 | {
129 | "idiom" : "mac",
130 | "size" : "128x128",
131 | "scale" : "2x"
132 | },
133 | {
134 | "idiom" : "mac",
135 | "size" : "256x256",
136 | "scale" : "1x"
137 | },
138 | {
139 | "idiom" : "mac",
140 | "size" : "256x256",
141 | "scale" : "2x"
142 | },
143 | {
144 | "idiom" : "mac",
145 | "size" : "512x512",
146 | "scale" : "1x"
147 | },
148 | {
149 | "idiom" : "mac",
150 | "size" : "512x512",
151 | "scale" : "2x"
152 | },
153 | {
154 | "size" : "24x24",
155 | "idiom" : "watch",
156 | "scale" : "2x",
157 | "role" : "notificationCenter",
158 | "subtype" : "38mm"
159 | },
160 | {
161 | "size" : "27.5x27.5",
162 | "idiom" : "watch",
163 | "scale" : "2x",
164 | "role" : "notificationCenter",
165 | "subtype" : "42mm"
166 | },
167 | {
168 | "size" : "29x29",
169 | "idiom" : "watch",
170 | "role" : "companionSettings",
171 | "scale" : "2x"
172 | },
173 | {
174 | "size" : "29x29",
175 | "idiom" : "watch",
176 | "role" : "companionSettings",
177 | "scale" : "3x"
178 | },
179 | {
180 | "size" : "40x40",
181 | "idiom" : "watch",
182 | "scale" : "2x",
183 | "role" : "appLauncher",
184 | "subtype" : "38mm"
185 | },
186 | {
187 | "size" : "44x44",
188 | "idiom" : "watch",
189 | "scale" : "2x",
190 | "role" : "appLauncher",
191 | "subtype" : "40mm"
192 | },
193 | {
194 | "size" : "50x50",
195 | "idiom" : "watch",
196 | "scale" : "2x",
197 | "role" : "appLauncher",
198 | "subtype" : "44mm"
199 | },
200 | {
201 | "size" : "86x86",
202 | "idiom" : "watch",
203 | "scale" : "2x",
204 | "role" : "quickLook",
205 | "subtype" : "38mm"
206 | },
207 | {
208 | "size" : "98x98",
209 | "idiom" : "watch",
210 | "scale" : "2x",
211 | "role" : "quickLook",
212 | "subtype" : "42mm"
213 | },
214 | {
215 | "size" : "108x108",
216 | "idiom" : "watch",
217 | "scale" : "2x",
218 | "role" : "quickLook",
219 | "subtype" : "44mm"
220 | },
221 | {
222 | "size" : "1024x1024",
223 | "idiom" : "watch-marketing",
224 | "filename" : "Artboard.png",
225 | "scale" : "1x"
226 | }
227 | ],
228 | "info" : {
229 | "version" : 1,
230 | "author" : "xcode"
231 | }
232 | }
--------------------------------------------------------------------------------
/babel/library/Generator.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
4 |
5 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
6 |
7 | var Sharp = require('sharp');
8 | var Fs = require('fs');
9 | var Os = require('os');
10 | var rimraf = require('rimraf');
11 | var Shell = require('electron').shell;
12 | var execSync = require('child_process').execSync;
13 |
14 | var Generator = function () {
15 | function Generator() {
16 | _classCallCheck(this, Generator);
17 | }
18 |
19 | _createClass(Generator, [{
20 | key: 'generate',
21 | value: function generate(originalImagePath, choice) {
22 | var _this = this;
23 |
24 | var contentsJson = this.makeContentsJson(choice);
25 | var downloadPath = Os.homedir().concat('/Downloads');
26 | var folderPath = downloadPath.concat('/AppIcon.appiconset');
27 |
28 | this.writeFolder(folderPath);
29 | this.writeContentsJson(contentsJson, folderPath);
30 | this.writeImages(originalImagePath, contentsJson, folderPath).then(function () {
31 | console.log(originalImagePath, folderPath, choice);
32 | if (choice === 'macOS (Icns)') {
33 | var iconsetPath = downloadPath.concat('/AppIcon.iconset');
34 | Fs.renameSync(folderPath, iconsetPath);
35 | Fs.unlinkSync(iconsetPath + '/Contents.json');
36 | execSync('iconutil -c icns ' + iconsetPath);
37 | _this.showFinder(iconsetPath);
38 | if (Fs.existsSync(iconsetPath)) {
39 | rimraf.sync(iconsetPath);
40 | }
41 | } else {
42 | _this.showFinder(folderPath);
43 | }
44 | });
45 | }
46 |
47 | // Helper
48 |
49 | }, {
50 | key: 'showFinder',
51 | value: function showFinder(path) {
52 | Shell.showItemInFolder(path);
53 | }
54 | }, {
55 | key: 'writeFolder',
56 | value: function writeFolder(folderPath) {
57 | if (Fs.existsSync(folderPath)) {
58 | rimraf.sync(folderPath);
59 | }
60 |
61 | Fs.mkdirSync(folderPath);
62 | }
63 | }, {
64 | key: 'writeContentsJson',
65 | value: function writeContentsJson(json, folderPath) {
66 | var path = folderPath.concat('/Contents.json');
67 | Fs.writeFileSync(path, JSON.stringify(json, null, 2).replace(new RegExp(": ", "g"), " : "));
68 | }
69 | }, {
70 | key: 'writeImages',
71 | value: function writeImages(originalImagePath, contentsJson, folderPath) {
72 | return Promise.all(contentsJson.images.map(function (object) {
73 | var output = folderPath.concat('/' + object.filename);
74 | var size = object.size.split('x')[0];
75 | var scale = object.scale.replace('x', '');
76 | var finalSize = size * scale;
77 |
78 | return new Promise(function (resolve) {
79 | Sharp(originalImagePath).resize(finalSize, finalSize).toFile(output, function (error, info) {
80 | resolve();
81 | });
82 | });
83 | }));
84 | }
85 | }, {
86 | key: 'makeContentsJson',
87 | value: function makeContentsJson(choice) {
88 | var idioms = this.idioms(choice);
89 | var content = Fs.readFileSync(__dirname + '/Contents.json', 'utf8');
90 | var json = JSON.parse(content);
91 |
92 | json.images = json.images.filter(function (object) {
93 | return idioms.includes(object.idiom);
94 | }).map(function (object) {
95 | var size = object.size.split('x')[0];
96 | object.filename = 'icon_' + size + '@' + object.scale + '.png';
97 |
98 | return object;
99 | });
100 |
101 | return json;
102 | }
103 | }, {
104 | key: 'idioms',
105 | value: function idioms(choice) {
106 | switch (choice) {
107 | case 'iOS (iPhone)':
108 | return ['iphone', 'ios-marketing'];
109 | case 'iOS (iPad)':
110 | return ['ipad', 'ios-marketing'];
111 | case 'iOS (Universal)':
112 | return ['iphone', 'ipad', 'ios-marketing'];
113 | case 'macOS':
114 | return ['mac'];
115 | case 'macOS (Icns)':
116 | return ['mac'];
117 | case 'watchOS':
118 | return ['watch', 'watch-marketing'];
119 | default:
120 | return [];
121 | }
122 | }
123 | }]);
124 |
125 | return Generator;
126 | }();
127 |
128 | module.exports = Generator;
--------------------------------------------------------------------------------
/babel/main.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var _require = require('electron'),
4 | app = _require.app,
5 | BrowserWindow = _require.BrowserWindow;
6 |
7 | var path = require('path');
8 | var url = require('url');
9 | var Menu = require('electron').Menu;
10 |
11 | // global
12 | var win = void 0;
13 |
14 | process.env['ELECTRON_DISABLE_SECURITY_WARNINGS'] = 'true';
15 |
16 | function createWindow() {
17 | win = new BrowserWindow({
18 | title: 'Icon Generator',
19 | width: 600,
20 | height: 500,
21 | resizable: false,
22 | icon: __dirname + '/Icon/Icon.icns',
23 | webPreferences: {
24 | nodeIntegration: true
25 | }
26 | });
27 |
28 | win.loadURL(url.format({
29 | pathname: path.join(__dirname, '../index.html'),
30 | protocol: 'file:',
31 | slashes: true
32 | }));
33 |
34 | win.on('closed', function () {
35 | win = null;
36 | });
37 |
38 | createMenu();
39 | }
40 |
41 | app.on('ready', createWindow);
42 |
43 | app.on('window-all-closed', function () {
44 | if (process.platform !== 'darwin') {
45 | app.quit();
46 | }
47 | });
48 |
49 | app.on('activate', function () {
50 | if (win === null) {
51 | createWindow();
52 | }
53 | });
54 |
55 | function createMenu() {
56 | var application = {
57 | label: "Icon Generator",
58 | submenu: [{
59 | label: "New",
60 | accelerator: "Command+N",
61 | click: function click() {
62 | if (win === null) {
63 | createWindow();
64 | }
65 | }
66 | }, {
67 | type: "separator"
68 | }, {
69 | label: "Quit",
70 | accelerator: "Command+Q",
71 | click: function click() {
72 | app.quit();
73 | }
74 | }]
75 | };
76 |
77 | var template = [application];
78 |
79 | Menu.setApplicationMenu(Menu.buildFromTemplate(template));
80 | }
--------------------------------------------------------------------------------
/babel/renderer.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var React = require('react');
4 | var ReactDOM = require('react-dom');
5 | var Application = require('./components/Application.js');
6 | var Path = require('path');
7 |
8 | // Reload
9 | function reload() {
10 | var state = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : { file: null, error: null };
11 |
12 | ReactDOM.render(React.createElement(Application, state), document.getElementById('root'));
13 | }
14 |
15 | // Drag and Drop
16 | function handleDragDrop() {
17 | document.addEventListener('drop', function (e) {
18 | e.preventDefault();
19 | e.stopPropagation();
20 |
21 | if (e.dataTransfer.files.length <= 0) {
22 | return;
23 | }
24 |
25 | var file = e.dataTransfer.files[0];
26 | var extension = Path.extname(file.path).replace('.', '').toLowerCase();
27 | var support = ['png', 'jpeg', 'jpg', 'webp', 'tiff', 'gif', 'svg'];
28 |
29 | if (support.includes(extension)) {
30 | reload({
31 | file: file,
32 | error: null
33 | });
34 | } else {
35 | reload({
36 | file: null,
37 | error: 'This file is not yet supported!'
38 | });
39 | }
40 | });
41 |
42 | document.addEventListener('dragover', function (e) {
43 | e.preventDefault();
44 | e.stopPropagation();
45 | });
46 | }
47 |
48 | // Initially
49 | handleDragDrop();
50 | reload();
--------------------------------------------------------------------------------
/entitlements.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | com.apple.security.cs.allow-jit
6 |
7 | com.apple.security.cs.allow-unsigned-executable-memory
8 |
9 | com.apple.security.cs.allow-dyld-environment-variables
10 |
11 |
12 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
20 |
21 |
22 |
23 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "icon_generator",
3 | "version": "1.4.0",
4 | "description": "A macOS app to generate app icons",
5 | "main": "babel/main.js",
6 | "repository": "https://github.com/onmyway133/IconGenerator",
7 | "author": "Khoa Pham",
8 | "license": "MIT",
9 | "scripts": {
10 | "start": "npm run babel && electron .",
11 | "babel": "babel ./src --out-dir ./babel --copy-files",
12 | "dist": "npm run babel && electron-builder"
13 | },
14 | "build": {
15 | "appId": "com.onmyway133.IconGenerator",
16 | "buildVersion": "21",
17 | "productName": "Icon Generator",
18 | "icon": "./Icon/Icon.icns",
19 | "mac": {
20 | "category": "public.app-category.productivity",
21 | "hardenedRuntime": true,
22 | "gatekeeperAssess": false,
23 | "entitlements": "./entitlements.plist",
24 | "entitlementsInherit": "./entitlements.plist"
25 | },
26 | "win": {
27 | "target": "msi"
28 | },
29 | "linux": {
30 | "target": [
31 | "AppImage",
32 | "deb"
33 | ]
34 | },
35 | "afterSign": "./afterSignHook.js"
36 | },
37 | "dependencies": {
38 | "@babel/plugin-transform-runtime": "^7.6.2",
39 | "@material-ui/core": "^4.4.2",
40 | "babel-polyfill": "^6.26.0",
41 | "image-size": "^0.6.1",
42 | "material-ui": "^0.20.2",
43 | "react": "^16.9.0",
44 | "react-dom": "^16.9.0",
45 | "rimraf": "^2.7.1",
46 | "sharp": "^0.23.0"
47 | },
48 | "devDependencies": {
49 | "babel": "^6.23.0",
50 | "babel-cli": "^6.26.0",
51 | "babel-core": "^6.26.3",
52 | "babel-loader": "^7.1.5",
53 | "babel-plugin-transform-runtime": "^6.23.0",
54 | "babel-preset-env": "^1.7.0",
55 | "babel-preset-react": "^6.24.1",
56 | "electron": "^5.0.10",
57 | "electron-builder": "^21.2.0",
58 | "electron-packager": "^14.0.6",
59 | "electron-rebuild": "^1.8.6"
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/src/components/Application.js:
--------------------------------------------------------------------------------
1 | const React = require('react')
2 | const ReactDOM = require('react-dom')
3 | const Paper = require('@material-ui/core/Paper')
4 | const MuiThemeProvider = require('material-ui/styles/').MuiThemeProvider
5 | const InputComponent = require('./InputComponent.js')
6 |
7 | // http://www.material-ui.com/#/get-started/installation
8 | // injectTapEventPlugin()
9 |
10 | class Application extends React.Component {
11 | constructor(props) {
12 | super(props)
13 | }
14 |
15 | render() {
16 | const styles = {
17 | div: {
18 | width: '100%',
19 | alignSelf: 'stretch',
20 | display: 'flex',
21 | backgroundcolor: '#F8F8F0'
22 | }
23 | }
24 |
25 | return (
26 |
27 |
28 |
29 |
30 |
31 | )
32 | }
33 | }
34 |
35 | module.exports = Application
--------------------------------------------------------------------------------
/src/components/InputComponent.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import ReactDOM from 'react-dom'
3 | import Paper from '@material-ui/core/Paper'
4 | import RadioGroup from '@material-ui/core/RadioGroup'
5 | import Radio from '@material-ui/core/Radio'
6 | import Button from '@material-ui/core/Button'
7 | import FormControlLabel from '@material-ui/core/FormControlLabel';
8 | import DialogContentText from '@material-ui/core/DialogContentText'
9 | import sizeOf from 'image-size'
10 | import Generator from '../library/Generator'
11 |
12 | class InputComponent extends React.Component {
13 | constructor(props) {
14 | super(props)
15 |
16 | this.state = {
17 | choice: 'iOS (iPhone)'
18 | }
19 |
20 | this.handleChoiceChange = this.handleChoiceChange.bind(this)
21 | this.handleGenerate = this.handleGenerate.bind(this)
22 | }
23 |
24 | render() {
25 | const styles = {
26 | div: {
27 | display: 'flex',
28 | alignSelf: 'stretch',
29 | width: '100%'
30 | }
31 | }
32 |
33 | return (
34 |
35 | {this.makeImage()}
36 | {this.makeChoices()}
37 |
38 | )
39 | }
40 |
41 | // action
42 |
43 | handleChoiceChange(event, value) {
44 | this.setState({
45 | choice: value
46 | })
47 | }
48 |
49 | handleGenerate() {
50 | const generator = new Generator()
51 | generator.generate(this.props.file.path, this.state.choice)
52 | }
53 |
54 | // make
55 |
56 | makeImage() {
57 | const divOptions = {
58 | style: {
59 | flex: 1.5,
60 | padding: '10px'
61 | }
62 | }
63 |
64 | const paperOptions = {
65 | style: {
66 | width: '100%',
67 | height: '100%'
68 | }
69 | }
70 |
71 | return (
72 |
73 |
74 | {this.makeImageElement()}
75 | {this.makeImageDescriptionElement()}
76 |
77 |
78 | )
79 | }
80 |
81 | makeImageElement() {
82 | let path
83 | if (this.props.file !== null) {
84 | path = this.props.file.path
85 | } else {
86 | path = ''
87 | }
88 |
89 | const styles = {
90 | div: {
91 | display: 'flex',
92 | justifyContent: 'center',
93 | paddingTop: '20px'
94 | },
95 | image: {
96 | width: '300px',
97 | height: '300px',
98 | border: '1px solid black'
99 | }
100 | }
101 |
102 | return (
103 |
104 |

105 |
106 | )
107 | }
108 |
109 | makeImageDescriptionElement() {
110 | let text
111 | if (this.props.file !== null) {
112 | const size = sizeOf(this.props.file.path)
113 | const sizeDescription = size.width + 'x' + size.height
114 | text = this.props.file.name + ' (' + sizeDescription + ')'
115 | } else if (this.props.error !== null) {
116 | text = this.props.error
117 | } else {
118 | text = 'Drag image onto the above box. Prefer 1024x1024 or larger'
119 | }
120 |
121 | const styles = {
122 | div: {
123 | display: 'flex',
124 | justifyContent: 'center',
125 | marginTop: '10px'
126 | },
127 | text: {
128 | textAlign: 'center'
129 | }
130 | }
131 |
132 | return (
133 |
134 | {text}
135 |
136 | )
137 | }
138 |
139 | makeChoices() {
140 | const styles = {
141 | div: {
142 | flex: 1,
143 | padding: '10px'
144 | },
145 | paper: {
146 | paddingTop: '10px',
147 | paddingBottom: '10px'
148 | },
149 | group: {
150 | paddingLeft: '10px'
151 | }
152 | }
153 |
154 | const choices = [
155 | "iOS (iPhone)", "iOS (iPad)", "iOS (Universal)", "macOS", "macOS (Icns)", "watchOS"
156 | ]
157 |
158 | const choiceElements = choices.map((name) => {
159 | return (
160 | } label={name} key={name} />
161 | )
162 | })
163 |
164 | return (
165 |
166 |
167 |
172 | {this.makeGenerateButton()}
173 |
174 |
175 | )
176 | }
177 |
178 | makeGenerateButton() {
179 | const styles = {
180 | div: {
181 | display: 'flex',
182 | justifyContent: 'center',
183 | marginTop: '10px'
184 | },
185 | button: {
186 | width: '80%'
187 | }
188 | }
189 |
190 | return (
191 |
192 |
200 |
201 | )
202 | }
203 | }
204 |
205 | module.exports = InputComponent
--------------------------------------------------------------------------------
/src/library/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "iphone",
5 | "size" : "20x20",
6 | "scale" : "2x"
7 | },
8 | {
9 | "idiom" : "iphone",
10 | "size" : "20x20",
11 | "scale" : "3x"
12 | },
13 | {
14 | "idiom" : "iphone",
15 | "size" : "29x29",
16 | "scale" : "2x"
17 | },
18 | {
19 | "idiom" : "iphone",
20 | "size" : "29x29",
21 | "scale" : "3x"
22 | },
23 | {
24 | "idiom" : "iphone",
25 | "size" : "40x40",
26 | "scale" : "2x"
27 | },
28 | {
29 | "idiom" : "iphone",
30 | "size" : "40x40",
31 | "scale" : "3x"
32 | },
33 | {
34 | "idiom" : "iphone",
35 | "size" : "60x60",
36 | "scale" : "2x"
37 | },
38 | {
39 | "idiom" : "iphone",
40 | "size" : "60x60",
41 | "scale" : "3x"
42 | },
43 | {
44 | "idiom" : "ipad",
45 | "size" : "20x20",
46 | "scale" : "1x"
47 | },
48 | {
49 | "idiom" : "ipad",
50 | "size" : "20x20",
51 | "scale" : "2x"
52 | },
53 | {
54 | "idiom" : "ipad",
55 | "size" : "29x29",
56 | "scale" : "1x"
57 | },
58 | {
59 | "idiom" : "ipad",
60 | "size" : "29x29",
61 | "scale" : "2x"
62 | },
63 | {
64 | "idiom" : "ipad",
65 | "size" : "40x40",
66 | "scale" : "1x"
67 | },
68 | {
69 | "idiom" : "ipad",
70 | "size" : "40x40",
71 | "scale" : "2x"
72 | },
73 | {
74 | "idiom" : "ipad",
75 | "size" : "76x76",
76 | "scale" : "1x"
77 | },
78 | {
79 | "idiom" : "ipad",
80 | "size" : "76x76",
81 | "scale" : "2x"
82 | },
83 | {
84 | "idiom" : "ipad",
85 | "size" : "83.5x83.5",
86 | "scale" : "2x"
87 | },
88 | {
89 | "idiom" : "ios-marketing",
90 | "size" : "1024x1024",
91 | "scale" : "1x"
92 | },
93 | {
94 | "idiom" : "car",
95 | "size" : "60x60",
96 | "scale" : "2x"
97 | },
98 | {
99 | "idiom" : "car",
100 | "size" : "60x60",
101 | "scale" : "3x"
102 | },
103 | {
104 | "idiom" : "mac",
105 | "size" : "16x16",
106 | "scale" : "1x"
107 | },
108 | {
109 | "idiom" : "mac",
110 | "size" : "16x16",
111 | "scale" : "2x"
112 | },
113 | {
114 | "idiom" : "mac",
115 | "size" : "32x32",
116 | "scale" : "1x"
117 | },
118 | {
119 | "idiom" : "mac",
120 | "size" : "32x32",
121 | "scale" : "2x"
122 | },
123 | {
124 | "idiom" : "mac",
125 | "size" : "128x128",
126 | "scale" : "1x"
127 | },
128 | {
129 | "idiom" : "mac",
130 | "size" : "128x128",
131 | "scale" : "2x"
132 | },
133 | {
134 | "idiom" : "mac",
135 | "size" : "256x256",
136 | "scale" : "1x"
137 | },
138 | {
139 | "idiom" : "mac",
140 | "size" : "256x256",
141 | "scale" : "2x"
142 | },
143 | {
144 | "idiom" : "mac",
145 | "size" : "512x512",
146 | "scale" : "1x"
147 | },
148 | {
149 | "idiom" : "mac",
150 | "size" : "512x512",
151 | "scale" : "2x"
152 | },
153 | {
154 | "size" : "24x24",
155 | "idiom" : "watch",
156 | "scale" : "2x",
157 | "role" : "notificationCenter",
158 | "subtype" : "38mm"
159 | },
160 | {
161 | "size" : "27.5x27.5",
162 | "idiom" : "watch",
163 | "scale" : "2x",
164 | "role" : "notificationCenter",
165 | "subtype" : "42mm"
166 | },
167 | {
168 | "size" : "29x29",
169 | "idiom" : "watch",
170 | "role" : "companionSettings",
171 | "scale" : "2x"
172 | },
173 | {
174 | "size" : "29x29",
175 | "idiom" : "watch",
176 | "role" : "companionSettings",
177 | "scale" : "3x"
178 | },
179 | {
180 | "size" : "40x40",
181 | "idiom" : "watch",
182 | "scale" : "2x",
183 | "role" : "appLauncher",
184 | "subtype" : "38mm"
185 | },
186 | {
187 | "size" : "44x44",
188 | "idiom" : "watch",
189 | "scale" : "2x",
190 | "role" : "appLauncher",
191 | "subtype" : "40mm"
192 | },
193 | {
194 | "size" : "50x50",
195 | "idiom" : "watch",
196 | "scale" : "2x",
197 | "role" : "appLauncher",
198 | "subtype" : "44mm"
199 | },
200 | {
201 | "size" : "86x86",
202 | "idiom" : "watch",
203 | "scale" : "2x",
204 | "role" : "quickLook",
205 | "subtype" : "38mm"
206 | },
207 | {
208 | "size" : "98x98",
209 | "idiom" : "watch",
210 | "scale" : "2x",
211 | "role" : "quickLook",
212 | "subtype" : "42mm"
213 | },
214 | {
215 | "size" : "108x108",
216 | "idiom" : "watch",
217 | "scale" : "2x",
218 | "role" : "quickLook",
219 | "subtype" : "44mm"
220 | },
221 | {
222 | "size" : "1024x1024",
223 | "idiom" : "watch-marketing",
224 | "filename" : "Artboard.png",
225 | "scale" : "1x"
226 | }
227 | ],
228 | "info" : {
229 | "version" : 1,
230 | "author" : "xcode"
231 | }
232 | }
--------------------------------------------------------------------------------
/src/library/Generator.js:
--------------------------------------------------------------------------------
1 | const Sharp = require('sharp')
2 | const Fs = require('fs')
3 | const Os = require('os')
4 | const rimraf = require('rimraf')
5 | const Shell = require('electron').shell
6 | const execSync = require('child_process').execSync
7 |
8 | class Generator {
9 |
10 | generate(originalImagePath, choice) {
11 | const contentsJson = this.makeContentsJson(choice)
12 | const downloadPath = Os.homedir().concat('/Downloads')
13 | const folderPath = downloadPath.concat('/AppIcon.appiconset')
14 |
15 | this.writeFolder(folderPath)
16 | this.writeContentsJson(contentsJson, folderPath)
17 | this.writeImages(originalImagePath, contentsJson, folderPath).then(() => {
18 | console.log(originalImagePath, folderPath, choice)
19 | if (choice === 'macOS (Icns)') {
20 | const iconsetPath = downloadPath.concat('/AppIcon.iconset')
21 | Fs.renameSync(folderPath, iconsetPath)
22 | Fs.unlinkSync(iconsetPath + '/Contents.json')
23 | execSync('iconutil -c icns ' + iconsetPath)
24 | this.showFinder(iconsetPath)
25 | if (Fs.existsSync(iconsetPath)) {
26 | rimraf.sync(iconsetPath)
27 | }
28 | } else {
29 | this.showFinder(folderPath)
30 | }
31 | })
32 | }
33 |
34 | // Helper
35 |
36 | showFinder(path) {
37 | Shell.showItemInFolder(path)
38 | }
39 |
40 | writeFolder(folderPath) {
41 | if (Fs.existsSync(folderPath)) {
42 | rimraf.sync(folderPath)
43 | }
44 |
45 | Fs.mkdirSync(folderPath)
46 | }
47 |
48 | writeContentsJson(json, folderPath) {
49 | const path = folderPath.concat('/Contents.json')
50 | Fs.writeFileSync(path, JSON.stringify(json, null, 2).replace(new RegExp(": ", "g"), " : "))
51 | }
52 |
53 | writeImages(originalImagePath, contentsJson, folderPath) {
54 | return Promise.all(
55 | contentsJson.images.map((object) => {
56 | const output = folderPath.concat(`/${object.filename}`)
57 | const size = object.size.split('x')[0]
58 | const scale = object.scale.replace('x', '')
59 | const finalSize = size * scale
60 |
61 | return new Promise((resolve) => {
62 | Sharp(originalImagePath)
63 | .resize(finalSize, finalSize)
64 | .toFile(output, (error, info) => {
65 | resolve()
66 | })
67 | })
68 | })
69 | )
70 | }
71 |
72 | makeContentsJson(choice) {
73 | const idioms = this.idioms(choice)
74 | const content = Fs.readFileSync(__dirname + '/Contents.json', 'utf8')
75 | const json = JSON.parse(content)
76 |
77 | json.images = json.images
78 | .filter((object) => {
79 | return idioms.includes(object.idiom)
80 | }).map((object) => {
81 | const size = object.size.split('x')[0]
82 | object.filename = `icon_${size}@${object.scale}.png`
83 |
84 | return object
85 | })
86 |
87 | return json
88 | }
89 |
90 | idioms(choice) {
91 | switch (choice) {
92 | case 'iOS (iPhone)':
93 | return ['iphone', 'ios-marketing']
94 | case 'iOS (iPad)':
95 | return ['ipad', 'ios-marketing']
96 | case 'iOS (Universal)':
97 | return ['iphone', 'ipad', 'ios-marketing']
98 | case 'macOS':
99 | return ['mac']
100 | case 'macOS (Icns)':
101 | return ['mac']
102 | case 'watchOS':
103 | return ['watch','watch-marketing']
104 | default:
105 | return []
106 | }
107 | }
108 | }
109 |
110 |
111 | module.exports = Generator
112 |
--------------------------------------------------------------------------------
/src/main.js:
--------------------------------------------------------------------------------
1 | const {app, BrowserWindow} = require('electron')
2 | const path = require('path')
3 | const url = require('url')
4 | const Menu = require('electron').Menu
5 |
6 | // global
7 | let win
8 |
9 | process.env['ELECTRON_DISABLE_SECURITY_WARNINGS'] = 'true';
10 |
11 | function createWindow () {
12 | win = new BrowserWindow({
13 | title: 'Icon Generator',
14 | width: 600,
15 | height: 500,
16 | resizable: false,
17 | icon: __dirname + '/Icon/Icon.icns',
18 | webPreferences: {
19 | nodeIntegration: true
20 | }
21 | })
22 |
23 | win.loadURL(url.format({
24 | pathname: path.join(__dirname, '../index.html'),
25 | protocol: 'file:',
26 | slashes: true
27 | }))
28 |
29 | win.on('closed', () => {
30 | win = null
31 | })
32 |
33 | createMenu()
34 | }
35 |
36 | app.on('ready', createWindow)
37 |
38 | app.on('window-all-closed', () => {
39 | if (process.platform !== 'darwin') {
40 | app.quit()
41 | }
42 | })
43 |
44 | app.on('activate', () => {
45 | if (win === null) {
46 | createWindow()
47 | }
48 | })
49 |
50 | function createMenu() {
51 | const application = {
52 | label: "Icon Generator",
53 | submenu: [
54 | {
55 | label: "New",
56 | accelerator: "Command+N",
57 | click: () => {
58 | if (win === null) {
59 | createWindow()
60 | }
61 | }
62 | },
63 | {
64 | type: "separator"
65 | },
66 | {
67 | label: "Quit",
68 | accelerator: "Command+Q",
69 | click: () => {
70 | app.quit()
71 | }
72 | }
73 | ]
74 | }
75 |
76 | const template = [
77 | application
78 | ]
79 |
80 | Menu.setApplicationMenu(Menu.buildFromTemplate(template))
81 | }
--------------------------------------------------------------------------------
/src/renderer.js:
--------------------------------------------------------------------------------
1 | const React = require('react')
2 | const ReactDOM = require('react-dom')
3 | const Application = require('./components/Application.js')
4 | const Path = require('path')
5 |
6 | // Reload
7 | function reload(state = {file: null, error: null}) {
8 | ReactDOM.render(
9 | React.createElement(Application, state),
10 | document.getElementById('root')
11 | )
12 | }
13 |
14 | // Drag and Drop
15 | function handleDragDrop() {
16 | document.addEventListener('drop', (e) => {
17 | e.preventDefault()
18 | e.stopPropagation()
19 |
20 | if (e.dataTransfer.files.length <= 0) {
21 | return
22 | }
23 |
24 | const file = e.dataTransfer.files[0]
25 | const extension = Path.extname(file.path).replace('.', '').toLowerCase()
26 | const support = ['png', 'jpeg', 'jpg', 'webp', 'tiff', 'gif', 'svg']
27 |
28 | if (support.includes(extension)) {
29 | reload({
30 | file,
31 | error: null
32 | })
33 | } else {
34 | reload({
35 | file: null,
36 | error: 'This file is not yet supported!'
37 | })
38 | }
39 | })
40 |
41 | document.addEventListener('dragover', (e) => {
42 | e.preventDefault()
43 | e.stopPropagation()
44 | })
45 | }
46 |
47 | // Initially
48 | handleDragDrop()
49 | reload()
--------------------------------------------------------------------------------