This commit is contained in:
Chen Asraf
2019-04-10 23:57:34 +03:00
parent 67d2b2f2a0
commit 55ace8c138
9 changed files with 354 additions and 35 deletions

26
bin/scaffold Executable file
View File

@@ -0,0 +1,26 @@
#!/usr/bin/env node
const SimpleScaffold = require('simple-scaffold').default
const path = require('path')
const args = process.argv.slice(2)
function pascalCase(str) {
str = String(str)
return str[0].toUpperCase() + str.slice(1).replace(/_([a-z])/gi, (_, letter) => letter.toUpperCase())
}
const [type, name] = args
const outputMap = {
component: path.join('src', 'components')
}
new SimpleScaffold({
name,
templates: [path.join(process.cwd(), 'templates', type, '**', '*')],
output: path.join(process.cwd(), outputMap[type] || ''),
locals: {
clsName: pascalCase(name)
},
createSubfolder: false,
}).run()

View File

@@ -20,6 +20,7 @@
"babel-loader": "^8.0.5",
"chrome-url-loader": "^1.1.1",
"copy-webpack-plugin": "^5.0.2",
"simple-scaffold": "^0.5.0",
"webpack": "^4.29.6",
"webpack-cli": "^3.3.0"
},

View File

@@ -1,13 +1,19 @@
import 'chrome'
import '@babel/polyfill'
import { createImageElement, getImageSize } from './lib/resizer'
import { assert, Message } from './lib/utils'
chrome.contextMenus.create({
contexts: ['image'],
title: 'Resize to 50%',
onclick: async (info, tab) => {
assert(info.mediaType === 'image', 'expected image')
chrome.tabs.sendMessage(tab.id, new Message('select_image', { src: info.srcUrl }))
function item(title, ratio) {
return {
contexts: ['image'],
title,
onclick: async (info, tab) => {
assert(info.mediaType === 'image', 'expected image')
chrome.tabs.sendMessage(tab.id, new Message('select_image', { src: info.srcUrl, ratio }))
}
}
})
}
chrome.contextMenus.create(item('Resize to 25%', 0.25))
chrome.contextMenus.create(item('Resize to 50%', 0.5))
chrome.contextMenus.create(item('Resize to 75%', 0.75))
chrome.contextMenus.create(item('Custom resize...'))

View File

@@ -0,0 +1,54 @@
import * as React from 'react'
import propTypes from 'prop-types'
import styled from 'styled-components'
import { Size } from '../lib/resizer'
const Container = styled.div``
export default class ImageCanvas extends React.Component {
constructor(props) {
super(props)
// this.state = {
// //
// }
}
componentDidMount() {
this.redraw()
}
componentWillUpdate(props) {
this.redraw({ newSize: props.size })
}
canvasCtx() {
return this.canvasRef && this.canvasRef.getContext('2d')
}
redraw({ newSize } = {}) {
const { image } = this.props
let { size } = this.props
size = newSize || size
const ctx = this.canvasCtx()
this.canvasRef.width = size.width
this.canvasRef.height = size.height
ctx.clearRect(0, 0, size.width, size.height)
ctx.drawImage(image, 0, 0, size.width, size.height)
}
render() {
return (
<Container>
<canvas ref={(ref) => this.canvasRef = ref}></canvas>
</Container>
)
}
}
ImageCanvas.propTypes = {
image: propTypes.instanceOf(Image).isRequired,
size: propTypes.oneOfType([
propTypes.instanceOf(Size),
propTypes.number,
])
}

View File

@@ -3,6 +3,7 @@ import propTypes from 'prop-types'
import styled from 'styled-components'
import { spacing, MAX_Z_INDEX } from '../lib/cssvars'
import { Size, createImageElement, getImageSize } from '../lib/resizer'
import ImageCanvas from './image_canvas.jsx'
const Overlay = styled.div`
position: fixed;
@@ -12,6 +13,8 @@ const Overlay = styled.div`
bottom: 0;
background: rgba(0, 0 , 0, 0.4);
z-index: ${MAX_Z_INDEX};
font-family: Helvetica, Arial, sans-serif;
font-size: 14px;
&, & * {
box-sizing: border-box;
@@ -32,61 +35,115 @@ const Container = styled.div`
const boxPadding = spacing * 2;
const boxMargin = spacing * 2;
const Box = styled.div`
min-width: 500px;
max-width: calc(100vw - ${boxMargin * 2}px);
max-height: calc(100vh - ${boxMargin * 2}px);
min-width: 500px;
margin: ${boxMargin}px;
padding: ${boxMargin / 2}px;
background: white;
border-radius: 3px;
padding: ${boxPadding}px;
overflow: auto;
`
const TopBar = styled.div``
const BottomBar = styled.div``
const BoxContent = styled.div`
max-height: calc(100vh - ${boxMargin * 2}px - ${boxPadding * 2}px);
overflow-y: auto;
max-width: calc(100vw - ${boxMargin * 2}px);
max-height: calc(100vh - 200px);
overflow: auto;
`
const ImageContainer = styled.div`
max-width: 100%;
overflow: auto;
const ImageContainer = styled.div``
const Metadata = styled.div`
display: flex;
margin-top: ${spacing * 2}px;
`
const Chip = styled.span`
display: inline-block;
background: #eee;
border-radius: 1em;
padding: 6px 9px;
font-size: 0.95em;
&:not(:last-child) {
margin-right: ${spacing}px;
}
`
export default class UploadResizer extends React.Component {
constructor(props) {
super(props)
this.state = {
size: new Size(),
imageSize: new Size(),
customSize: new Size(),
image: null,
}
}
async componentWillMount() {
const img = await createImageElement(this.props.image)
this.setState({ size: getImageSize(img) })
const image = await createImageElement(this.props.image)
const imageSize = getImageSize(image);
const customSize = imageSize.resize(this.props.ratio)
this.setState({ image, imageSize, customSize })
}
render() {
const { size } = this.state
const { imageSize, customSize, image } = this.state
const { onClose } = this.props
return (
<Overlay>
<Container>
<Overlay onClick={(e) => onClose && onClose(e)}>
<Container onClick={(e) => e.stopPropagation()}>
<Box>
<BoxContent>
Image Preview:
<ImageContainer>
<img src={this.props.image} />
</ImageContainer>
<TopBar>
Final Size Preview
</TopBar>
<div>
Original Size: {size.width}x{size.height}
</div>
<BoxContent>
<ImageContainer>
{image ? <ImageCanvas image={image} size={customSize} /> : 'Loading...'}
</ImageContainer>
</BoxContent>
<Metadata>
<Chip>
Original Size: {imageSize.width}x{imageSize.height}
</Chip>
<Chip>
New Size: {customSize.width}x{customSize.height}
<select onChange={(e) => this.changeRatio(e.target.value)}
defaultValue={this.props.ratio}>
{[0.25, 0.5, 0.75].map((ratio) => {
return (
<option key={ratio} value={ratio}>
{ratio * 100}% of original
</option>
)
})}
<option value={1}>Original Size</option>
</select>
</Chip>
</Metadata>
</Box>
</Container>
</Overlay>
)
}
changeRatio(ratio) {
const { imageSize } = this.state
const customSize = imageSize.resize(Number(ratio))
this.setState({ customSize })
}
}
UploadResizer.propTypes = {
image: propTypes.string.isRequired,
ratio: propTypes.oneOfType([
propTypes.instanceOf(Size),
propTypes.number,
]),
onClose: propTypes.func,
}

View File

@@ -6,12 +6,18 @@ import UploadResizer from './components/upload_resizer'
const __EL_ID = '__upload_resizer_box'
function bootstrap(imgSrc) {
function bootstrap(imgSrc, ratio) {
removeExistingBootstrap()
const el = document.createElement('div')
el.id = __EL_ID
document.body.appendChild(el)
ReactDOM.render(<UploadResizer image={imgSrc} />, el)
const mainComponent = (
<UploadResizer
image={imgSrc}
ratio={ratio}
onClose={() => removeExistingBootstrap()} />
)
ReactDOM.render(mainComponent, el)
}
function removeExistingBootstrap() {
@@ -24,7 +30,7 @@ function removeExistingBootstrap() {
chrome.runtime.onMessage.addListener((message, sender, respond) => {
console.log('got msg', message, sender)
if (message && message.action === 'select_image') {
bootstrap(message.payload.src)
bootstrap(message.payload.src, message.payload.ratio)
}
return true
})

View File

@@ -32,4 +32,17 @@ export class Size {
this.height = height
}
}
resize(ratio) {
if (ratio === undefined) {
return new Size(this.width, this.height)
}
if (typeof ratio === 'number') {
return new Size(this.width * ratio, this.height * ratio)
}
if (ratio instanceof Size) {
return ratio
}
throw new Error('bad ratio value')
}
}

View File

@@ -0,0 +1,26 @@
import * as React from 'react'
import propTypes from 'prop-types'
import styled from 'styled-components'
const Container = styled.div``
export default class {{clsName}} extends React.Component {
constructor(props) {
super(props)
// this.state = {
// //
// }
}
render() {
return (
<Container>
{{name}} component
</Container>
)
}
}
{{clsName}}.propTypes = {
//
};

136
yarn.lock
View File

@@ -950,6 +950,18 @@ arr-union@^3.1.0:
resolved "https://registry.yarnpkg.com/arr-union/-/arr-union-3.1.0.tgz#e39b09aea9def866a8f206e288af63919bae39c4"
integrity sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=
array-back@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/array-back/-/array-back-2.0.0.tgz#6877471d51ecc9c9bfa6136fb6c7d5fe69748022"
integrity sha512-eJv4pLLufP3g5kcZry0j6WXpIbzYw9GUB4mVJZno9wfwiBxbizTnHCw3VJb07cBihbFX48Y7oSrW9y+gt4glyw==
dependencies:
typical "^2.6.1"
array-back@^3.0.1:
version "3.1.0"
resolved "https://registry.yarnpkg.com/array-back/-/array-back-3.1.0.tgz#b8859d7a508871c9a7b2cf42f99428f65e96bfb0"
integrity sha512-TkuxA4UCOvxuDK6NZYXCalszEzj+TLszyASooky+i742l9TqsOdYCMJJupxRic61hwquNtppB3hgcuq9SVSH1Q==
array-union@^1.0.1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/array-union/-/array-union-1.0.2.tgz#9a34410e4f4e3da23dea375be5be70f24778ec39"
@@ -1350,7 +1362,27 @@ color-name@1.1.3:
resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25"
integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=
commander@^2.19.0:
command-line-args@^5.0.2:
version "5.1.1"
resolved "https://registry.yarnpkg.com/command-line-args/-/command-line-args-5.1.1.tgz#88e793e5bb3ceb30754a86863f0401ac92fd369a"
integrity sha512-hL/eG8lrll1Qy1ezvkant+trihbGnaKaeEjj6Scyr3DN+RC7iQ5Rz84IeLERfAWDGo0HBSNAakczwgCilDXnWg==
dependencies:
array-back "^3.0.1"
find-replace "^3.0.0"
lodash.camelcase "^4.3.0"
typical "^4.0.0"
command-line-usage@^5.0.5:
version "5.0.5"
resolved "https://registry.yarnpkg.com/command-line-usage/-/command-line-usage-5.0.5.tgz#5f25933ffe6dedd983c635d38a21d7e623fda357"
integrity sha512-d8NrGylA5oCXSbGoKz05FkehDAzSmIm4K03S5VDh4d5lZAtTWfc3D1RuETtuQCn8129nYfJfDdF7P/lwcz1BlA==
dependencies:
array-back "^2.0.0"
chalk "^2.4.1"
table-layout "^0.4.3"
typical "^2.6.1"
commander@^2.19.0, commander@~2.20.0:
version "2.20.0"
resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.0.tgz#d58bb2b5c1ee8f87b0d340027e9e94e222c5a422"
integrity sha512-7j2y+40w61zy6YC2iRNpUe/NwhNyoXrYpHMrSunaMG64nRnaf96zO/KMQR4OyN/UnE5KLyEBnKHd4aG3rskjpQ==
@@ -1575,7 +1607,7 @@ decode-uri-component@^0.2.0:
resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545"
integrity sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=
deep-extend@^0.6.0:
deep-extend@^0.6.0, deep-extend@~0.6.0:
version "0.6.0"
resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac"
integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==
@@ -1877,6 +1909,13 @@ find-cache-dir@^2.0.0:
make-dir "^2.0.0"
pkg-dir "^3.0.0"
find-replace@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/find-replace/-/find-replace-3.0.0.tgz#3e7e23d3b05167a76f770c9fbd5258b0def68c38"
integrity sha512-6Tb2myMioCAgv5kfvP5/PkZZ/ntTpVK39fHY7WkWBgvbeE+VHd/tZuZ4mrC+bxh4cfOZeYKVPaJIZtZXV7GNCQ==
dependencies:
array-back "^3.0.1"
find-up@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/find-up/-/find-up-3.0.0.tgz#49169f1d7993430646da61ecc5ae355c21c97b73"
@@ -2050,6 +2089,17 @@ graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2:
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.15.tgz#ffb703e1066e8a0eeaa4c8b80ba9253eeefbfb00"
integrity sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA==
handlebars@^4.1.0:
version "4.1.1"
resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.1.1.tgz#6e4e41c18ebe7719ae4d38e5aca3d32fa3dd23d3"
integrity sha512-3Zhi6C0euYZL5sM0Zcy7lInLXKQ+YLcF/olbN010mzGQ4XVm50JeyBnMqofHh696GrciGruC7kCcApPDJvVgwA==
dependencies:
neo-async "^2.6.0"
optimist "^0.6.1"
source-map "^0.6.1"
optionalDependencies:
uglify-js "^3.1.4"
has-flag@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd"
@@ -2495,6 +2545,16 @@ locate-path@^3.0.0:
p-locate "^3.0.0"
path-exists "^3.0.0"
lodash.camelcase@^4.3.0:
version "4.3.0"
resolved "https://registry.yarnpkg.com/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz#b28aa6288a2b9fc651035c7711f65ab6190331a6"
integrity sha1-soqmKIorn8ZRA1x3EfZathkDMaY=
lodash.padend@^4.6.1:
version "4.6.1"
resolved "https://registry.yarnpkg.com/lodash.padend/-/lodash.padend-4.6.1.tgz#53ccba047d06e158d311f45da625f4e49e6f166e"
integrity sha1-U8y6BH0G4VjTEfRdpiX05J5vFm4=
lodash@^3.5.0:
version "3.10.1"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-3.10.1.tgz#5bf45e8e49ba4189e17d482789dfd15bd140b7b6"
@@ -2641,6 +2701,11 @@ minimist@^1.2.0:
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284"
integrity sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=
minimist@~0.0.1:
version "0.0.10"
resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.10.tgz#de3f98543dbf96082be48ad1a0c7cda836301dcf"
integrity sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8=
minipass@^2.2.1, minipass@^2.3.4:
version "2.3.5"
resolved "https://registry.yarnpkg.com/minipass/-/minipass-2.3.5.tgz#cacebe492022497f656b0f0f51e2682a9ed2d848"
@@ -2745,7 +2810,7 @@ needle@^2.2.1:
iconv-lite "^0.4.4"
sax "^1.2.4"
neo-async@^2.5.0:
neo-async@^2.5.0, neo-async@^2.6.0:
version "2.6.0"
resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.0.tgz#b9d15e4d71c6762908654b5183ed38b753340835"
integrity sha512-MFh0d/Wa7vkKO3Y3LlacqAEeHK0mckVqzDieUKTT+KGxi+zIpeVsFxymkIiRpbpDziHc290Xr9A1O4Om7otoRA==
@@ -2910,6 +2975,14 @@ once@^1.3.0, once@^1.3.1, once@^1.4.0:
dependencies:
wrappy "1"
optimist@^0.6.1:
version "0.6.1"
resolved "https://registry.yarnpkg.com/optimist/-/optimist-0.6.1.tgz#da3ea74686fa21a19a111c326e90eb15a0196686"
integrity sha1-2j6nRob6IaGaERwybpDrFaAZZoY=
dependencies:
minimist "~0.0.1"
wordwrap "~0.0.2"
os-browserify@^0.3.0:
version "0.3.0"
resolved "https://registry.yarnpkg.com/os-browserify/-/os-browserify-0.3.0.tgz#854373c7f5c2315914fc9bfc6bd8238fdda1ec27"
@@ -3265,6 +3338,11 @@ readdirp@^2.2.1:
micromatch "^3.1.10"
readable-stream "^2.0.2"
reduce-flatten@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/reduce-flatten/-/reduce-flatten-1.0.1.tgz#258c78efd153ddf93cb561237f61184f3696e327"
integrity sha1-JYx479FT3fk8tWEjf2EYTzaW4yc=
regenerate-unicode-properties@^8.0.2:
version "8.0.2"
resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-8.0.2.tgz#7b38faa296252376d363558cfbda90c9ce709662"
@@ -3527,6 +3605,16 @@ signal-exit@^3.0.0:
resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d"
integrity sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=
simple-scaffold@^0.5.0:
version "0.5.0"
resolved "https://registry.yarnpkg.com/simple-scaffold/-/simple-scaffold-0.5.0.tgz#4f9f2af7275c8041923b6bdd5385f6f6cabec12c"
integrity sha512-J5w6mt8C5OlJY4fFrKf04cHPGsvjMZGUqTnOR+3XZVoS2Iha5+nMGmfxa4W37JSO6IF//lR7JtW+NhIKcHfqnQ==
dependencies:
command-line-args "^5.0.2"
command-line-usage "^5.0.5"
glob "^7.1.3"
handlebars "^4.1.0"
slash@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/slash/-/slash-1.0.0.tgz#c41f2f6c39fc16d1cd17ad4b5d896114ae470d55"
@@ -3744,6 +3832,17 @@ supports-color@^5.3.0, supports-color@^5.5.0:
dependencies:
has-flag "^3.0.0"
table-layout@^0.4.3:
version "0.4.4"
resolved "https://registry.yarnpkg.com/table-layout/-/table-layout-0.4.4.tgz#bc5398b2a05e58b67b05dd9238354b89ef27be0f"
integrity sha512-uNaR3SRMJwfdp9OUr36eyEi6LLsbcTqTO/hfTsNviKsNeyMBPICJCC7QXRF3+07bAP6FRwA8rczJPBqXDc0CkQ==
dependencies:
array-back "^2.0.0"
deep-extend "~0.6.0"
lodash.padend "^4.6.1"
typical "^2.6.1"
wordwrapjs "^3.0.0"
tapable@^1.0.0, tapable@^1.1.0:
version "1.1.1"
resolved "https://registry.yarnpkg.com/tapable/-/tapable-1.1.1.tgz#4d297923c5a72a42360de2ab52dadfaaec00018e"
@@ -3855,6 +3954,24 @@ typedarray@^0.0.6:
resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=
typical@^2.6.1:
version "2.6.1"
resolved "https://registry.yarnpkg.com/typical/-/typical-2.6.1.tgz#5c080e5d661cbbe38259d2e70a3c7253e873881d"
integrity sha1-XAgOXWYcu+OCWdLnCjxyU+hziB0=
typical@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/typical/-/typical-4.0.0.tgz#cbeaff3b9d7ae1e2bbfaf5a4e6f11eccfde94fc4"
integrity sha512-VAH4IvQ7BDFYglMd7BPRDfLgxZZX4O4TFcRDA6EN5X7erNJJq+McIEp8np9aVtxrCJ6qx4GTYVfOWNjcqwZgRw==
uglify-js@^3.1.4:
version "3.5.4"
resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.5.4.tgz#4a64d57f590e20a898ba057f838dcdfb67a939b9"
integrity sha512-GpKo28q/7Bm5BcX9vOu4S46FwisbPbAmkkqPnGIpKvKTM96I85N6XHQV+k4I6FA2wxgLhcsSyHoNhzucwCflvA==
dependencies:
commander "~2.20.0"
source-map "~0.6.1"
unicode-canonical-property-names-ecmascript@^1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz#2619800c4c825800efdd8343af7dd9933cbe2818"
@@ -4075,6 +4192,19 @@ wide-align@^1.1.0:
dependencies:
string-width "^1.0.2 || 2"
wordwrap@~0.0.2:
version "0.0.3"
resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-0.0.3.tgz#a3d5da6cd5c0bc0008d37234bbaf1bed63059107"
integrity sha1-o9XabNXAvAAI03I0u68b7WMFkQc=
wordwrapjs@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/wordwrapjs/-/wordwrapjs-3.0.0.tgz#c94c372894cadc6feb1a66bff64e1d9af92c5d1e"
integrity sha512-mO8XtqyPvykVCsrwj5MlOVWvSnCdT+C+QVbm6blradR7JExAhbkZ7hZ9A+9NUtwzSqrlUo9a67ws0EiILrvRpw==
dependencies:
reduce-flatten "^1.0.1"
typical "^2.6.1"
worker-farm@^1.5.2:
version "1.6.0"
resolved "https://registry.yarnpkg.com/worker-farm/-/worker-farm-1.6.0.tgz#aecc405976fab5a95526180846f0dba288f3a4a0"