├── .compilerc
├── .gitignore
├── .nvmrc
├── .travis.yml
├── .vscode
├── launch.json
└── settings.json
├── CNAME
├── Info.plist
├── README.md
├── fixtures
└── foo.ttf
├── lib
├── __tests__
│ └── font.test.ts
├── css.ts
├── font-collection.ts
├── font.ts
├── ttf.ts
├── woff.ts
└── woff2.ts
├── package-lock.json
├── package.json
├── src
├── app.tsx
├── assets
│ ├── build
│ │ ├── dmg-background-combined.tiff
│ │ ├── dmg-background.tiff
│ │ └── dmg-background@2x.tiff
│ └── system-icons
│ │ ├── icon.icns
│ │ ├── icon.png
│ │ ├── icon_64x64.png
│ │ └── icon_old.icns
├── checkupdates.js
├── index.html
├── index.ts
├── process-fonts.ts
└── styles
│ └── app.css
├── tsconfig.json
└── tslint.json
/.compilerc:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "development": {
4 | "text/typescript": {
5 | "removeComments": false,
6 | "preserveConstEnums": true,
7 | "declaration": true,
8 | "noImplicitAny": true,
9 | "noImplicitReturns": true,
10 | "suppressImplicitAnyIndexErrors": true,
11 | "strictNullChecks": true,
12 | "noUnusedLocals": true,
13 | "noImplicitThis": true,
14 | "noUnusedParameters": true,
15 | "inlineSourceMap": true,
16 | "inlineSources": true,
17 | "importHelpers": true,
18 | "noEmitHelpers": true,
19 | "experimentalDecorators": true,
20 | "target": "es2015",
21 | "module": "commonjs",
22 | "jsx": "react"
23 | }
24 | },
25 | "production": {
26 | "text/typescript": {
27 | "removeComments": false,
28 | "preserveConstEnums": true,
29 | "declaration": true,
30 | "noImplicitAny": true,
31 | "noImplicitReturns": true,
32 | "suppressImplicitAnyIndexErrors": true,
33 | "strictNullChecks": true,
34 | "noUnusedLocals": true,
35 | "noImplicitThis": true,
36 | "noUnusedParameters": true,
37 | "sourceMap": false,
38 | "importHelpers": true,
39 | "noEmitHelpers": true,
40 | "experimentalDecorators": true,
41 | "target": "es2015",
42 | "jsx": "react"
43 | }
44 | }
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /node_modules
2 | /out
3 | /build
4 | *.log
5 | .DS_Store
--------------------------------------------------------------------------------
/.nvmrc:
--------------------------------------------------------------------------------
1 | 8.6.0
2 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - 8
4 |
5 | install:
6 | - npm install
7 |
8 | script:
9 | - npm run lint
10 | - npm run test
11 |
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "0.2.0",
3 | "configurations": [
4 | {
5 | "name": "Debug Main Process",
6 | "type": "node",
7 | "request": "launch",
8 | "cwd": "${workspaceRoot}",
9 | "runtimeExecutable": "${workspaceRoot}/node_modules/.bin/electron",
10 | "windows": {
11 | "runtimeExecutable": "${workspaceRoot}/node_modules/.bin/electron.cmd"
12 | },
13 | "args" : ["."]
14 | }
15 | ]
16 | }
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "typescript.format.enable": false
3 | }
4 |
--------------------------------------------------------------------------------
/CNAME:
--------------------------------------------------------------------------------
1 | www.fontplop.com
2 |
--------------------------------------------------------------------------------
/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDocumentTypes
6 |
7 |
8 | CFBundleTypeExtensions
9 |
10 | ttf
11 | woff
12 | eot
13 |
14 | CFBundleTypeIconFile
15 |
16 | CFBundleTypeName
17 | Font files
18 | CFBundleTypeRole
19 | Editor
20 | LSHandlerRank
21 | Alternate
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | ---
7 |
8 | [](#backers) [](#sponsors) [](https://travis-ci.org/matthewgonzalez/fontplop)
9 |
10 | FontPlop is an OSX/macOS application which takes `ttf` and `otf` files and outputs a webfont bundle: `woff2`, `woff`, `ttf`/`otf`. It is the simpler, faster, free successor to [FontPrep](http://www.fontprep.com), which is no longer maintained.
11 |
12 | ### Demo
13 |
14 |
15 |
16 |
17 | ### Installation
18 |
19 | `brew cask install fontplop`
20 |
21 |
22 | ### Testing
23 |
24 | Run lint (tslint) and tests (Jest) like so:
25 |
26 | ```sh
27 | npm run lint
28 | npm run test
29 | ```
30 |
31 | ### Generated CSS (recommended)
32 |
33 | _Stolen directly from the Typekit site 💂💰_
34 |
35 | ```css
36 |
37 | @font-face {
38 | font-family:"My Font";
39 | src:url("my-font.woff2") format("woff2"),url("my-font.woff") format("woff"),url("my-font.otf") format("opentype");
40 | font-style:normal;font-weight:400;
41 | }
42 | ```
43 |
44 | **Why not EOT or SVG?** Well, they're essentially no longer needed and/or dying off. See
45 | [this issue](https://github.com/matthewgonzalez/fontplop/issues/17). If you _really_ need EOT/SVG
46 | fonts, you can download version [1.1.0](https://github.com/matthewgonzalez/fontplop/releases/tag/v1.1.0)
47 | where those formats were last supported.
48 |
49 | ### Donations
50 |
51 | Help us keep FontPlop free and open source by making a donation. Thanks – it helps us continue
52 | to build awesome stuff.
53 |
54 | Via OpenCollective: https://opencollective.com/fontplop
55 |
56 | Via Bitcoin: `182JQcPACPh3bf6iQE73KB4Dvv5na6zep4`
57 |
58 |
59 | ### Building fontplop.com
60 |
61 | fontplop.com is hosted on Github pages. Publish changes like so:
62 |
63 | 1. Checkout `site` branch, which is a simple `create-react-app` app
64 | 2. Make changes
65 | 3. `npm run deploy` to publish to github
66 |
67 |
68 | ### Generating Installation Background
69 |
70 | To accomodate for retina/non-retina screens, a multipage `TIFF` should be generated from two files.
71 |
72 | ```$ cd src/assets/build```
73 |
74 | ```$ tiffutil -catnosizecheck dmg-background.tiff dmg-background@2x.tiff -out dmg-background-combined.tiff```
75 |
76 | ### Credits
77 |
78 | #### Contributors
79 |
80 | This project exists thanks to all the people who contribute.
81 |
82 |
83 |
84 | #### Backers
85 |
86 | Thank you to all our backers! 🙏 [[Become a backer](https://opencollective.com/fontplop#backer)]
87 |
88 |
89 |
90 |
91 | #### Sponsors
92 |
93 | Support this project by becoming a sponsor. Your logo will show up here with a link to your website. [[Become a sponsor](https://opencollective.com/fontplop#sponsor)]
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 | ### License
109 |
110 | MIT
111 |
--------------------------------------------------------------------------------
/fixtures/foo.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/matthewgonzalez/fontplop/d6fde1b56fd3fa0dc684a9eb7ebd5577d5602cae/fixtures/foo.ttf
--------------------------------------------------------------------------------
/lib/__tests__/font.test.ts:
--------------------------------------------------------------------------------
1 | import { FontCollection } from '../font-collection'
2 | import { WOFF2 } from '../woff2'
3 | import * as path from 'path'
4 | import * as fs from 'fs'
5 |
6 | const fontPath = path.resolve(__dirname, '../../fixtures/foo.ttf')
7 | const fc = new FontCollection(fontPath)
8 |
9 | it('should create an output path', () => {
10 | expect(fs.existsSync(fc.outputPath)).toBeTruthy()
11 | fc.cleanup()
12 | })
13 |
14 | it('should create an output path', () => {
15 | expect(fc.isTTF).toBeTruthy()
16 | expect(fc.isOTF).not.toBeTruthy()
17 | })
18 |
19 | it('should have an extension', () => {
20 | const svg = new WOFF2(fontPath)
21 | expect(svg.ext).toBe('woff2')
22 | })
23 |
24 | it('should have a human font name', () => {
25 | expect(fc.fontNameHuman).not.toBeUndefined()
26 | expect(fc.fontNameHuman).not.toEqual(null)
27 | fc.cleanup()
28 | })
29 |
--------------------------------------------------------------------------------
/lib/css.ts:
--------------------------------------------------------------------------------
1 | import {
2 | Font
3 | } from './font'
4 | import * as fs from 'fs'
5 |
6 | export class CSS extends Font {
7 |
8 | export () {
9 | const nameWithoutExt = this.nameWithoutExt
10 | const fontNameHuman = this.fontNameHuman
11 |
12 | const cssOutput = `
13 | @font-face {
14 | font-family:"${fontNameHuman}";
15 | src:url("${nameWithoutExt}.woff2") format("woff2"),url("${nameWithoutExt}.woff") format("woff"),url("${nameWithoutExt}.ttf") format("truetype");
16 | font-style:normal;font-weight:400;
17 | }
18 | `
19 | fs.writeFileSync(this.outFile, cssOutput)
20 | }
21 |
22 | get ext () {
23 | return 'css'
24 | }
25 |
26 | }
27 |
--------------------------------------------------------------------------------
/lib/font-collection.ts:
--------------------------------------------------------------------------------
1 | import { Font } from './font'
2 | import { TTF } from './ttf'
3 | import { WOFF } from './woff'
4 | import { WOFF2 } from './woff2'
5 | import { CSS } from './css'
6 |
7 | import * as rimraf from 'rimraf'
8 |
9 | export class FontCollection extends Font {
10 |
11 | paths: Array
12 |
13 | export () {
14 | const path = this.ttfPath
15 |
16 | if (this.isOTF) {
17 | this.createOrphanTTF()
18 | this.copySelf()
19 | }
20 |
21 | new TTF(path).export()
22 | new WOFF(path).export()
23 | new WOFF2(path).export()
24 | new CSS(path).export()
25 |
26 | this.cleanupOrphansIfNecessary()
27 | }
28 |
29 | cleanup () {
30 | rimraf.sync(this.outputPath)
31 | }
32 |
33 | }
34 |
--------------------------------------------------------------------------------
/lib/font.ts:
--------------------------------------------------------------------------------
1 | import * as path from 'path'
2 | import * as replaceExt from 'replace-ext'
3 | import * as FontEditorCore from 'fonteditor-core'
4 |
5 | import * as fs from 'fs'
6 | import * as rimraf from 'rimraf'
7 | import { execSync } from 'child_process'
8 |
9 | export class Font {
10 |
11 | filePath: string
12 | hasOrphanTTF: boolean
13 |
14 | constructor (filePath: string) {
15 | this.filePath = filePath
16 | this.createOutputPath()
17 | }
18 |
19 | createOutputPath () {
20 | if (!fs.existsSync(this.outputPath)) {
21 | fs.mkdirSync(this.outputPath)
22 | }
23 | }
24 |
25 | readFont () {
26 | const inBuffer = fs.readFileSync(this.ttfPath)
27 | return FontEditorCore.Font.create(inBuffer, {
28 | type: 'ttf'
29 | })
30 | }
31 |
32 | export () {
33 |
34 | const font = this.readFont()
35 |
36 | const outBuffer = font.write({
37 | type: this.ext,
38 | hinting: true
39 | })
40 |
41 | fs.writeFileSync(this.outFile, outBuffer)
42 | }
43 |
44 | get inputExt (): string {
45 | return path.extname(this.filePath).replace('.', '')
46 | }
47 |
48 | get ext () {
49 | return 'ttf'
50 | }
51 |
52 | get outFile () {
53 | return `${this.outputPath}/${this.nameWithoutExt}.${this.ext}`
54 | }
55 |
56 | get outputPath () {
57 | const output = path.resolve(this.dir, `${this.nameWithoutExt}-export`)
58 | return output
59 | }
60 |
61 | get dir () {
62 | return path.dirname(this.filePath)
63 | }
64 |
65 | get basename () {
66 | return path.basename(this.filePath)
67 | }
68 |
69 | get nameWithoutExt () {
70 | return path.parse(this.basename).name
71 | }
72 |
73 | get fontNameHuman () {
74 | return this.readFont().data.name.fullName || this.nameWithoutExt
75 | }
76 |
77 | get isTTF () {
78 | return path.parse(this.filePath).ext === '.ttf'
79 | }
80 |
81 | get isOTF () {
82 | return path.parse(this.filePath).ext === '.otf'
83 | }
84 |
85 | hasTTF (): boolean {
86 | return fs.existsSync(this.ttfPath)
87 | }
88 |
89 | get ttfPath () {
90 | return replaceExt(this.filePath, '.ttf')
91 | }
92 |
93 | copySelf () {
94 | this.createOutputPath()
95 | execSync(`
96 | cp "${this.filePath}" "${this.outputPath}"
97 | `)
98 | }
99 |
100 | createOrphanTTF () {
101 | const inBuffer = fs.readFileSync(this.filePath)
102 |
103 | const font = FontEditorCore.Font.create(inBuffer, {
104 | type: this.inputExt
105 | })
106 |
107 | const outBuffer = font.write({
108 | type: this.ext,
109 | hinting: true
110 | })
111 |
112 | this.hasOrphanTTF = true
113 | fs.writeFileSync(this.ttfPath, outBuffer)
114 | }
115 |
116 | cleanupOrphansIfNecessary () {
117 | if (this.hasOrphanTTF) rimraf.sync(this.ttfPath)
118 | }
119 |
120 | }
121 |
--------------------------------------------------------------------------------
/lib/ttf.ts:
--------------------------------------------------------------------------------
1 | import { Font } from './font'
2 |
3 | export class TTF extends Font {
4 |
5 | get ext () {
6 | return 'ttf'
7 | }
8 |
9 | }
10 |
--------------------------------------------------------------------------------
/lib/woff.ts:
--------------------------------------------------------------------------------
1 | import { Font } from './font'
2 |
3 | export class WOFF extends Font {
4 |
5 | get ext () {
6 | return 'woff'
7 | }
8 |
9 | }
10 |
--------------------------------------------------------------------------------
/lib/woff2.ts:
--------------------------------------------------------------------------------
1 | import { Font } from './font'
2 | import * as ttf2woff2 from 'ttf2woff2'
3 | import * as fs from 'fs'
4 |
5 | export class WOFF2 extends Font {
6 |
7 | get ext () {
8 | return 'woff2'
9 | }
10 |
11 | export () {
12 | const input = fs.readFileSync(this.ttfPath)
13 | fs.writeFileSync(this.outFile, ttf2woff2(input))
14 | return this.outFile
15 | }
16 |
17 | }
18 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "fontplop",
3 | "productName": "fontplop",
4 | "version": "1.4.0",
5 | "description": "Drag'n'drop font converter.",
6 | "main": "src/index.ts",
7 | "scripts": {
8 | "start": "electron-forge start",
9 | "package": "electron-forge package",
10 | "make": "electron-forge make",
11 | "lint": "tslint --project tsconfig.json --type-check --force --fix",
12 | "publish": "electron-forge publish",
13 | "test": "jest",
14 | "postinstall": "opencollective postinstall"
15 | },
16 | "keywords": [],
17 | "contributors": [
18 | {
19 | "name": "Matthew Gonzalez",
20 | "email": "me@matthewgonzalez.me",
21 | "url": "http://matthewgonzalez.me"
22 | },
23 | {
24 | "name": "Brian Gonzalez",
25 | "email": "me@briangonzalez.org",
26 | "url": "http://www.briangonzalez.org"
27 | }
28 | ],
29 | "license": "MIT",
30 | "config": {
31 | "forge": {
32 | "make_targets": {
33 | "win32": [
34 | "squirrel"
35 | ],
36 | "darwin": [
37 | "dmg"
38 | ],
39 | "linux": [
40 | "deb",
41 | "rpm"
42 | ]
43 | },
44 | "electronPackagerConfig": {
45 | "packageManager": "npm",
46 | "extendInfo": "Info.plist",
47 | "icon": "src/assets/system-icons/icon.icns",
48 | "name": "fontplop"
49 | },
50 | "electronWinstallerConfig": {
51 | "name": "fontplop"
52 | },
53 | "electronInstallerDMG": {
54 | "background": "src/assets/build/dmg-background-combined.tiff",
55 | "icon": "src/assets/system-icons/icon.icns"
56 | },
57 | "electronInstallerDebian": {},
58 | "electronInstallerRedhat": {},
59 | "github_repository": {
60 | "owner": "matthewgonzalez",
61 | "name": "fontplop"
62 | },
63 | "windowsStoreConfig": {
64 | "packageName": "",
65 | "name": "fontplop"
66 | }
67 | }
68 | },
69 | "dependencies": {
70 | "electron-compile": "^6.4.2",
71 | "electron-devtools-installer": "^2.2.0",
72 | "electron-json-storage": "^3.2.0",
73 | "fonteditor-core": "0.0.37",
74 | "github-version-checker": "github:matthewgonzalez/github-version-checker",
75 | "opencollective": "^1.0.3",
76 | "react": "^16.0.0",
77 | "react-dom": "^16.0.0",
78 | "react-dropzone": "^4.1.3",
79 | "react-hot-loader": "^3.0.0-beta.7",
80 | "replace-ext": "^1.0.0",
81 | "rimraf": "^2.6.2",
82 | "tslib": "^1.7.1",
83 | "ttf2woff2": "^2.0.3"
84 | },
85 | "devDependencies": {
86 | "@types/electron": "^1.6.10",
87 | "@types/electron-devtools-installer": "^2.0.2",
88 | "@types/jest": "^21.1.2",
89 | "@types/react": "16.0.10",
90 | "@types/react-dom": "^16.0.1",
91 | "@types/react-dropzone": "^4.1.0",
92 | "@types/replace-ext": "0.0.27",
93 | "@types/rimraf": "^2.0.2",
94 | "babel-plugin-transform-async-to-generator": "^6.24.1",
95 | "babel-preset-env": "^1.6.0",
96 | "babel-preset-react": "^6.24.1",
97 | "electron-forge": "^4.1.2",
98 | "electron-prebuilt-compile": "1.7.8",
99 | "jest": "^21.2.1",
100 | "ts-jest": "^21.1.0",
101 | "tslint": "^5.7.0",
102 | "tslint-config-standard": "^7.0.0",
103 | "typescript": "^2.5.3"
104 | },
105 | "jest": {
106 | "transform": {
107 | "^.+\\.tsx?$": "/node_modules/ts-jest/preprocessor.js"
108 | },
109 | "testRegex": "(/__tests__/.*|(\\.|/)(test|spec))\\.(jsx?|tsx?)$",
110 | "moduleFileExtensions": [
111 | "ts",
112 | "tsx",
113 | "js",
114 | "jsx",
115 | "json"
116 | ]
117 | },
118 | "collective": {
119 | "type": "opencollective",
120 | "url": "https://opencollective.com/fontplop",
121 | "logo": "https://opencollective.com/fontplop/logo.txt"
122 | }
123 | }
124 |
--------------------------------------------------------------------------------
/src/app.tsx:
--------------------------------------------------------------------------------
1 | import { ipcRenderer } from 'electron'
2 | import * as React from 'react'
3 | import * as Dropzone from 'react-dropzone'
4 |
5 | export const validExtensions = ['.otf', '.ttf']
6 |
7 | export class App extends React.Component {
8 |
9 | validExtensions: Array
10 |
11 | constructor (props: any) {
12 | super(props)
13 |
14 | this.validExtensions = validExtensions
15 | this.state = {
16 | dragOver: false
17 | }
18 | }
19 |
20 | onDrop (acceptedFiles: Array /* rejected: any */) {
21 | const acceptedFilePaths = acceptedFiles.map(f => f.path)
22 | ipcRenderer.send('process-fonts', acceptedFilePaths)
23 | this.setState({ dragOver: false })
24 | }
25 |
26 | onDragOver () {
27 | this.setState({ dragOver: true })
28 | }
29 |
30 | onDragLeave () {
31 | this.setState({ dragOver: false })
32 | }
33 |
34 | public render () {
35 | const dzStyle = {
36 | position: 'fixed',
37 | top: '1px',
38 | left: '1px',
39 | right: '1px',
40 | bottom: '1px',
41 | borderRadius: '3px',
42 | borderWidth: '1px',
43 | borderStyle: 'solid',
44 | borderColor: '#087D9B',
45 | justifyContent: 'center',
46 | display: 'flex',
47 | alignItems: 'center',
48 | textAlign: 'center',
49 | fontSize: '24px',
50 | lineHeight: '1em',
51 | userSelect: 'none',
52 | cursor: 'default'
53 | }
54 | const dzActiveStyle = {
55 | backgroundColor: 'green',
56 | borderColor: 'green'
57 | }
58 |
59 | const titleStyle = {
60 | fontSize: 20,
61 | marginBottom: 10
62 | }
63 |
64 | const subtitleStyle = {
65 | fontSize: 10,
66 | clear: 'both',
67 | color: 'rgba(255,255,255,0.5)',
68 | padding: '0px 20px',
69 | lineHeight: '1.75em'
70 | }
71 |
72 | const snippetStyle = {
73 | backgroundColor: this.state.dragOver ? 'rgba(0,0,0,0.1)' : 'rgba(255,255,255,0.1)',
74 | borderRadius: 2,
75 | padding: '2px 5px',
76 | display: 'inline block',
77 | marginRight: '2px'
78 | }
79 |
80 | return (
81 |
90 |
91 |
Plop here
92 |
93 | Must be of type {(() => this.validExtensions.map((ext, index) => {ext}))()}
94 |
95 |
96 |
97 | )
98 | }
99 | }
100 |
--------------------------------------------------------------------------------
/src/assets/build/dmg-background-combined.tiff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/matthewgonzalez/fontplop/d6fde1b56fd3fa0dc684a9eb7ebd5577d5602cae/src/assets/build/dmg-background-combined.tiff
--------------------------------------------------------------------------------
/src/assets/build/dmg-background.tiff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/matthewgonzalez/fontplop/d6fde1b56fd3fa0dc684a9eb7ebd5577d5602cae/src/assets/build/dmg-background.tiff
--------------------------------------------------------------------------------
/src/assets/build/dmg-background@2x.tiff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/matthewgonzalez/fontplop/d6fde1b56fd3fa0dc684a9eb7ebd5577d5602cae/src/assets/build/dmg-background@2x.tiff
--------------------------------------------------------------------------------
/src/assets/system-icons/icon.icns:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/matthewgonzalez/fontplop/d6fde1b56fd3fa0dc684a9eb7ebd5577d5602cae/src/assets/system-icons/icon.icns
--------------------------------------------------------------------------------
/src/assets/system-icons/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/matthewgonzalez/fontplop/d6fde1b56fd3fa0dc684a9eb7ebd5577d5602cae/src/assets/system-icons/icon.png
--------------------------------------------------------------------------------
/src/assets/system-icons/icon_64x64.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/matthewgonzalez/fontplop/d6fde1b56fd3fa0dc684a9eb7ebd5577d5602cae/src/assets/system-icons/icon_64x64.png
--------------------------------------------------------------------------------
/src/assets/system-icons/icon_old.icns:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/matthewgonzalez/fontplop/d6fde1b56fd3fa0dc684a9eb7ebd5577d5602cae/src/assets/system-icons/icon_old.icns
--------------------------------------------------------------------------------
/src/checkupdates.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | const {app, dialog, shell} = require('electron')
3 | const path = require('path')
4 | const version = app.getVersion()
5 | const versionCheck = require('github-version-checker')
6 | const storage = require('electron-json-storage')
7 |
8 | const repo = 'matthewgonzalez/fontplop'
9 | const iconPath = 'assets/system-icons/icon_64x64.png'
10 | const updateURL = 'http://fontplop.com/'
11 |
12 | function showUpdateMessageBox (update) {
13 | let message = 'Check it. ' + update.tag_name + ' is hot off the press. You are currently rocking v' + version + '. You might want to go and grab it.'
14 |
15 | // Ask user to update the app
16 | dialog.showMessageBox({
17 | type: 'question',
18 | icon: path.join(__dirname, iconPath),
19 | buttons: [
20 | 'Take me there. Right now.', 'Remind me.', 'No, thanks. I don\'t want this update.'
21 | ],
22 | defaultId: 0,
23 | cancelId: 1,
24 | message: 'The new ' + app.getName() + ' is available 😃',
25 | detail: message
26 | }, response => {
27 | if (response === 0) {
28 | shell.openExternal(updateURL)
29 | } else if (response === 1) {
30 | storage.has('ignoreVersion', (error, hasKey) => {
31 | if (error) throw error
32 | if (hasKey) storage.remove('ignoreVersion', (error) => {
33 | if (error) throw error
34 | })
35 | })
36 | } else if (response === 2) {
37 | storage.set('ignoreVersion', update.tag_name, (error) => {
38 | if (error) throw error
39 | })
40 | }
41 | })
42 | }
43 |
44 | function showNoUpdateMessageBox () {
45 | let message = `Continue ploppin'.`
46 | dialog.showMessageBox({
47 | type: 'info',
48 | icon: path.join(__dirname, iconPath),
49 | buttons: [
50 | '¡Muchisimas gracias!'
51 | ],
52 | defaultId: 0,
53 | message: 'You\'re all up to date.',
54 | detail: message
55 | })
56 | }
57 |
58 | function startVersionCheck (options) {
59 | versionCheck(options.versionCheck, function (update) { // callback function
60 | if (update) {
61 | showUpdateMessageBox(update)
62 | } else if (options.forceCheck) {
63 | showNoUpdateMessageBox()
64 | }
65 | })
66 | }
67 |
68 | function checkUpdates () {
69 | const options = Object.assign({
70 | versionCheck: {
71 | // repo: 'atom/atom', // for test purposes
72 | repo,
73 | currentVersion: version
74 | },
75 | forceCheck: false
76 | }, arguments[0])
77 |
78 | if (options.forceCheck) {
79 | startVersionCheck(options)
80 | } else {
81 | storage.get('ignoreVersion', (error, data) => {
82 | if (error || typeof data !== 'string') return startVersionCheck(options)
83 |
84 | options.versionCheck.currentVersion = data.replace(/[^0-9$.,]/g, '')
85 |
86 | startVersionCheck(options)
87 | })
88 | }
89 | }
90 |
91 | module.exports = checkUpdates
92 |
--------------------------------------------------------------------------------
/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
27 |
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | import { app, BrowserWindow, ipcMain, Menu, shell, dialog } from 'electron'
2 | import installExtension, { REACT_DEVELOPER_TOOLS } from 'electron-devtools-installer'
3 | import { enableLiveReload } from 'electron-compile'
4 | import processFonts from './process-fonts'
5 | import * as path from 'path'
6 |
7 | const checkUpdates = require('./checkupdates')
8 |
9 | // Keep a global reference of the window object, if you don't, the window will
10 | // be closed automatically when the JavaScript object is garbage collected.
11 | let mainWindow: Electron.BrowserWindow | null = null
12 | const isDevMode = process.execPath.match(/[\\/]electron/)
13 |
14 | if (isDevMode) {
15 | enableLiveReload({ strategy: 'react-hmr' })
16 | }
17 |
18 | const createWindow = async () => {
19 | mainWindow = new BrowserWindow({
20 | width: isDevMode ? 800 : 200,
21 | height: isDevMode ? 800 : 200,
22 | resizable: !!isDevMode,
23 | show: false,
24 | backgroundColor: '#000',
25 | titleBarStyle: 'hiddenInset',
26 | fullscreen: false,
27 | fullscreenable: false,
28 | maximizable: false,
29 | icon: path.join(__dirname, 'src/fontplop-app-icon.png')
30 | })
31 |
32 | // and load the index.html of the app.
33 | mainWindow.loadURL(`file://${__dirname}/index.html`)
34 |
35 | // Open the DevTools.
36 | if (isDevMode) {
37 | await installExtension(REACT_DEVELOPER_TOOLS)
38 | mainWindow.webContents.openDevTools()
39 | }
40 |
41 | mainWindow.once('ready-to-show', () => {
42 | if (mainWindow) { mainWindow.show() }
43 | })
44 |
45 | // Emitted when the window is closed.
46 | mainWindow.on('closed', () => {
47 | // Dereference the window object, usually you would store windows
48 | // in an array if your app supports multi windows, this is the time
49 | // when you should delete the corresponding element.
50 | mainWindow = null
51 | })
52 |
53 | // Call auto-updater
54 | mainWindow.webContents.on('did-frame-finish-load', () => {
55 | // checkUpdates()
56 | if (!isDevMode) {
57 | checkUpdates()
58 | }
59 | })
60 |
61 | const showOpen = function () {
62 | dialog.showOpenDialog({
63 | properties: ['openFile', 'openDirectory', 'multiSelections'],
64 | filters: [{ name: 'fontplop', extensions: ['ttf', 'otf'] }]
65 | }, (filePaths) => {
66 | processFonts(filePaths)
67 | })
68 | }
69 |
70 | const menu = Menu.buildFromTemplate([
71 | {
72 | submenu: [
73 | {
74 | label: 'About fontplop',
75 | click: () => { shell.openExternal('http://www.fontplop.com') }
76 | },
77 | {
78 | label: 'Check for Updates...',
79 | click: () => checkUpdates({ forceCheck: true })
80 | },
81 | { type: 'separator' },
82 | { role: 'quit' }
83 | ]
84 | },
85 | {
86 | label: 'File',
87 | submenu: [
88 | {
89 | label: 'Open',
90 | click: function () { showOpen() }
91 | }
92 | ]
93 | }
94 | ])
95 | Menu.setApplicationMenu(menu)
96 |
97 | }
98 |
99 | // This method will be called when Electron has finished
100 | // initialization and is ready to create browser windows.
101 | // Some APIs can only be used after this event occurs.
102 | app.on('ready', createWindow)
103 |
104 | // Quit when all windows are closed.
105 | app.on('window-all-closed', () => {
106 | // On OS X it is common for applications and their menu bar
107 | // to stay active until the user quits explicitly with Cmd + Q
108 | if (process.platform !== 'darwin') {
109 | app.quit()
110 | }
111 | })
112 |
113 | app.on('activate', () => {
114 | // On OS X it's common to re-create a window in the app when the
115 | // dock icon is clicked and there are no other windows open.
116 | if (mainWindow === null) {
117 | createWindow()
118 | }
119 | })
120 |
121 | /*
122 | The following creates a listener to handle files dropped onto the window
123 | and files dropped onto app icon in dock or in Finder
124 | */
125 | ipcMain.on('process-fonts', (event: any, files: Array) => {
126 | event.preventDefault()
127 | processFonts(files)
128 | })
129 |
130 | app.on('open-file', function (event: any, filePaths: Array) {
131 | event.preventDefault()
132 |
133 | if (app.isReady()) {
134 | processFonts(filePaths)
135 | } else {
136 | app.once('ready', () => {
137 | processFonts(filePaths)
138 | })
139 | }
140 |
141 | })
142 |
--------------------------------------------------------------------------------
/src/process-fonts.ts:
--------------------------------------------------------------------------------
1 | import { FontCollection } from '../lib/font-collection'
2 |
3 | function processFonts (files: Array) {
4 | if (!files) return
5 | files = [].concat(files)
6 | files.forEach(file => {
7 | new FontCollection(file).export()
8 | })
9 | }
10 |
11 | export default processFonts
12 |
--------------------------------------------------------------------------------
/src/styles/app.css:
--------------------------------------------------------------------------------
1 | body {
2 | color: #fff;
3 | font-family: -apple-system, BlinkMacSystemFont, sans-serif;
4 | font-weight: 100;
5 | letter-spacing: .5px;
6 | }
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "removeComments": false,
4 | "preserveConstEnums": true,
5 | "sourceMap": true,
6 | "declaration": true,
7 | "noImplicitAny": true,
8 | "noImplicitReturns": true,
9 | "suppressImplicitAnyIndexErrors": true,
10 | "strictNullChecks": true,
11 | "noUnusedLocals": true,
12 | "noImplicitThis": true,
13 | "noUnusedParameters": true,
14 | "importHelpers": true,
15 | "noEmitHelpers": true,
16 | "module": "commonjs",
17 | "moduleResolution": "node",
18 | "pretty": true,
19 | "target": "es2015",
20 | "jsx": "react"
21 | },
22 | "formatCodeOptions": {
23 | "indentSize": 2,
24 | "tabSize": 2
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/tslint.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "tslint-config-standard"
3 | }
4 |
--------------------------------------------------------------------------------