├── src ├── ComicBubble.js ├── fonts │ ├── slkscr.woff │ └── slkscr.woff2 ├── index.js ├── components │ ├── Link.js │ ├── GitHub.js │ ├── Footer.js │ └── Icons.js ├── index.html ├── App.js └── index.css ├── README.md ├── .gitignore └── package.json /src/ComicBubble.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/fonts/slkscr.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/midudev/komic.app/master/src/fonts/slkscr.woff -------------------------------------------------------------------------------- /src/fonts/slkscr.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/midudev/komic.app/master/src/fonts/slkscr.woff2 -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import { h, render } from 'preact' 2 | import App from './App' 3 | 4 | render( 5 | , document.body 6 | ) -------------------------------------------------------------------------------- /src/components/Link.js: -------------------------------------------------------------------------------- 1 | import { h } from 'preact' 2 | 3 | export default function ({children, href, external}) { 4 | const rel = external ? 'noopener nofollow' : undefined 5 | return {children} 6 | } -------------------------------------------------------------------------------- /src/components/GitHub.js: -------------------------------------------------------------------------------- 1 | import {h} from 'preact' 2 | import Link from './Link' 3 | 4 | export default () => ( 5 |
6 | 7 | 8 | 9 |
10 | ) -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # komik.app 🗯️ 2 | Comic bubbles without hassle 3 | 4 | ## TODO 5 | - [x] Add Download functionality 6 | - [ ] Add Copy to Clipboard button. 7 | - [ ] Let user enable/disable text-transform uppercase. 8 | - [ ] Let user change spike direction. 9 | - [ ] Let user change size of bubble. 10 | - [ ] Add animation of writing when user enters 11 | - [ ] Be able to customize color of bubble and border. 12 | - [ ] Select between different fonts. 13 | -------------------------------------------------------------------------------- /src/components/Footer.js: -------------------------------------------------------------------------------- 1 | import { h } from 'preact' 2 | import Link from './Link' 3 | 4 | // · based on the mythical wigflip tool 5 | 6 | export default () => ( 7 |
8 |

developed by @midudev · changelog · feature requests

9 |
10 | ) -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Create your own Comic Bubble 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/components/Icons.js: -------------------------------------------------------------------------------- 1 | import { h } from 'preact' 2 | 3 | export const FloopyIcon = () => ( 4 | 5 | ) 6 | 7 | export const SettingsIcon = () => ( 8 | 9 | ) -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | firebase-debug.log* 8 | 9 | # Firebase cache 10 | .firebase/ 11 | 12 | # Firebase config 13 | 14 | # Uncomment this if you'd like others to create their own Firebase project. 15 | # For a team working on the same Firebase project(s), it is recommended to leave 16 | # it commented so all members can deploy to the same project(s) in .firebaserc. 17 | # .firebaserc 18 | 19 | # Runtime data 20 | pids 21 | *.pid 22 | *.seed 23 | *.pid.lock 24 | 25 | # Directory for instrumented libs generated by jscoverage/JSCover 26 | lib-cov 27 | 28 | # Coverage directory used by tools like istanbul 29 | coverage 30 | 31 | # nyc test coverage 32 | .nyc_output 33 | 34 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 35 | .grunt 36 | 37 | # Bower dependency directory (https://bower.io/) 38 | bower_components 39 | 40 | # node-waf configuration 41 | .lock-wscript 42 | 43 | # Compiled binary addons (http://nodejs.org/api/addons.html) 44 | build/Release 45 | 46 | # Dependency directories 47 | node_modules/ 48 | 49 | # Optional npm cache directory 50 | .npm 51 | 52 | # Optional eslint cache 53 | .eslintcache 54 | 55 | # Optional REPL history 56 | .node_repl_history 57 | 58 | # Output of 'npm pack' 59 | *.tgz 60 | 61 | # Yarn Integrity file 62 | .yarn-integrity 63 | 64 | # dotenv environment variables file 65 | .env 66 | 67 | # Parcel stuff 68 | .cache 69 | dist 70 | 71 | # Firebase stuff 72 | .firebase 73 | 74 | package-lock.json 75 | 76 | .DS_Store -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "comic-bubble", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "dev": "parcel src/index.html", 8 | "build": "parcel build", 9 | "test": "echo \"Error: no test specified\" && exit 1" 10 | }, 11 | "dependencies": { 12 | "downloadjs": "1.4.7", 13 | "file-drop-element": "0.2.0", 14 | "html-to-image": "0.1.1", 15 | "preact": "10.3.1" 16 | }, 17 | "author": "Miguel Ángel Durán García - @midudev", 18 | "license": "ISC", 19 | "devDependencies": { 20 | "babel-eslint": "10.0.3", 21 | "eslint": "6.8.0", 22 | "eslint-config-prettier": "6.10.0", 23 | "eslint-config-standard": "14.1.0", 24 | "eslint-config-standard-react": "9.2.0", 25 | "eslint-plugin-import": "2.20.1", 26 | "eslint-plugin-node": "11.0.0", 27 | "eslint-plugin-prettier": "3.1.2", 28 | "eslint-plugin-promise": "4.2.1", 29 | "eslint-plugin-react": "7.18.3", 30 | "eslint-plugin-standard": "4.0.1", 31 | "parcel-bundler": "1.12.4" 32 | }, 33 | "eslintConfig": { 34 | "extends": [ 35 | "standard", 36 | "standard-react", 37 | "prettier" 38 | ], 39 | "parser": "babel-eslint", 40 | "env": { 41 | "browser": true, 42 | "node": true, 43 | "es6": true 44 | }, 45 | "plugins": [ 46 | "react", 47 | "prettier" 48 | ], 49 | "settings": { 50 | "react": { 51 | "pragma": "h", 52 | "version": "preact" 53 | } 54 | }, 55 | "parserOptions": { 56 | "ecmaVersion": 2018, 57 | "ecmaFeatures": { 58 | "jsx": true 59 | } 60 | }, 61 | "rules": { 62 | "no-console": 1, 63 | "no-empty": 0, 64 | "semi": [ 65 | "error", 66 | "never" 67 | ], 68 | "keyword-spacing": 2, 69 | "react/prop-types": 0, 70 | "react/no-string-refs": 2, 71 | "react/no-find-dom-node": 2, 72 | "react/no-is-mounted": 2, 73 | "react/jsx-no-comment-textnodes": 2, 74 | "react/jsx-curly-spacing": 2, 75 | "react/jsx-no-undef": 2, 76 | "react/jsx-uses-react": 2, 77 | "react/jsx-uses-vars": 2 78 | } 79 | }, 80 | "prettier": { 81 | "trailingComma": "es5", 82 | "tabWidth": 2, 83 | "semi": false, 84 | "useTabs": true, 85 | "singleQuote": true, 86 | "endOfLine": "lf" 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/App.js: -------------------------------------------------------------------------------- 1 | import { h } from 'preact' 2 | import {useRef, useState, useEffect} from 'preact/hooks' 3 | 4 | import 'file-drop-element' 5 | import {FloopyIcon} from './components/Icons' 6 | import Footer from './components/Footer' 7 | import GitHub from './components/GitHub' 8 | 9 | export default function App () { 10 | const dropTargetRef = useRef() 11 | const imgRef = useRef() 12 | 13 | const [withImage, setWithImage] = useState(false) 14 | 15 | function downloadImage () { 16 | const selector = withImage ? '#result > div' : '#result-bubble' 17 | const result = document.querySelector(selector) 18 | 19 | Promise.all([ 20 | import('html-to-image'), 21 | import('downloadjs') 22 | ]).then(([{toPng}, download]) => { 23 | toPng(result).then(dataUrl => download(dataUrl, 'komic.png')) 24 | }) 25 | } 26 | 27 | useEffect(function () { 28 | document.querySelector('[autofocus]').focus() 29 | 30 | dropTargetRef.current.addEventListener('filedrop', (e) => { 31 | imgRef.current.removeAttribute('hidden') 32 | imgRef.current.src = URL.createObjectURL(e.files[0]) 33 | setWithImage(true) 34 | }) 35 | }, []) 36 | 37 | useEffect(function () { 38 | const dragItem = document.querySelector("#result-bubble") 39 | const container = document.querySelector("#result") 40 | 41 | let active = false 42 | let currentX 43 | let currentY 44 | let initialX 45 | let initialY 46 | let xOffset = 0 47 | let yOffset = 0 48 | 49 | function dragStart(event) { 50 | const from = event.type === 'touchstart' 51 | ? event.touches[0] 52 | : event 53 | 54 | initialX = from.clientX - xOffset 55 | initialY = from.clientY - yOffset 56 | 57 | if (event.target === dragItem) { 58 | active = true 59 | } 60 | } 61 | 62 | function dragEnd() { 63 | initialX = currentX 64 | initialY = currentY 65 | 66 | active = false 67 | } 68 | 69 | function drag(event) { 70 | if (active) { 71 | event.preventDefault() 72 | 73 | const from = event.type === 'touchmove' 74 | ? event.touches[0] 75 | : event 76 | 77 | currentX = from.clientX - initialX 78 | currentY = from.clientY - initialY 79 | 80 | xOffset = currentX 81 | yOffset = currentY 82 | 83 | setTranslate(currentX, currentY, dragItem) 84 | } 85 | } 86 | 87 | function setTranslate(xPos, yPos, el) { 88 | el.style.transform = "translate3d(" + xPos + "px, " + yPos + "px, 0)" 89 | } 90 | 91 | container.addEventListener("touchstart", dragStart, false) 92 | container.addEventListener("touchend", dragEnd, false) 93 | container.addEventListener("touchmove", drag, false) 94 | 95 | container.addEventListener("mousedown", dragStart, false) 96 | container.addEventListener("mouseup", dragEnd, false) 97 | container.addEventListener("mousemove", drag, false) 98 | 99 | return () => { 100 | container.removeEventListener("touchstart", dragStart, false) 101 | container.removeEventListener("touchend", dragEnd, false) 102 | container.removeEventListener("touchmove", drag, false) 103 | 104 | container.removeEventListener("mousedown", dragStart, false) 105 | container.removeEventListener("mouseup", dragEnd, false) 106 | container.removeEventListener("mousemove", drag, false) 107 | } 108 | }) 109 | 110 | return ( 111 | 112 |
113 |
114 | 115 |
116 |
117 | HEY! WRITE HERE YOUR CONTENT! ✍️ 118 |
119 |
120 |
121 |
122 |
123 | 127 |
128 |