Skip to content

Commit 3fc35bc

Browse files
committed
Twitter share image feature added: included new component 'TwitterButton' and Express server side
1 parent 0d963a0 commit 3fc35bc

File tree

11 files changed

+509
-23
lines changed

11 files changed

+509
-23
lines changed

app.js

+234
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,234 @@
1+
/**
2+
* Module dependencies.
3+
*/
4+
import { renderToString } from 'react-dom/server'
5+
import { Provider } from 'react-redux'
6+
import undoable from 'redux-undo'
7+
import reducer from './src/reducer'
8+
import App from './src/components/App'
9+
import gm from 'gm'
10+
11+
var express = require('express')
12+
, routes = require('./routes')
13+
, bodyParser = require('body-parser')
14+
, cookieParser = require('cookie-parser')
15+
, temp = require('temp')
16+
, fs = require('fs')
17+
, path = require('path')
18+
, Twitter = require('twitter')
19+
, OAuth= require('oauth').OAuth
20+
, session = require('express-session')
21+
, React = require('react')
22+
, redux = require('redux')
23+
;
24+
25+
var app = module.exports = express(),
26+
configData = JSON.parse(fs.readFileSync('config.json', 'utf8'));
27+
28+
/**
29+
* Configuration
30+
*/
31+
var env = process.env.NODE_ENV || 'development';
32+
if ('development' == env) {
33+
configData = configData.dev;
34+
} else {
35+
configData = configData.prod;
36+
}
37+
38+
var oa = new OAuth(
39+
"https://api.twitter.com/oauth/request_token",
40+
"https://api.twitter.com/oauth/access_token",
41+
configData.twitter.consumer_key,
42+
configData.twitter.consumer_secret,
43+
"1.0A",
44+
"http://127.0.0.1:3000/auth/twitter/callback",
45+
"HMAC-SHA1"
46+
);
47+
48+
app.set('views', __dirname + '/views');
49+
app.set('view engine', 'jade');
50+
app.use(express.static(__dirname + '/deploy'));
51+
app.use(bodyParser.json());
52+
app.use(bodyParser.urlencoded({ extended: true }));
53+
app.use(cookieParser());
54+
app.use(session({ secret: configData.express.session_secret, resave: true, saveUninitialized: true }));
55+
56+
/**
57+
* Routes
58+
*/
59+
app.get('/', handleRender);
60+
61+
app.post('/auth/twitter', function(req, res) {
62+
oa.getOAuthRequestToken(function(error, oauth_token, oauth_token_secret, results){
63+
if (error) {
64+
console.log(error);
65+
res.send("auth twitter: error")
66+
}
67+
else {
68+
// console.log(oauth_token);
69+
// console.log(oauth_token_secret);
70+
req.session.oauthRequestToken = oauth_token;
71+
req.session.oauthRequestTokenSecret = oauth_token_secret;
72+
req.session.cssData = req.body;
73+
74+
res.contentType('application/json');
75+
var data = JSON.stringify('https://twitter.com/oauth/authenticate?oauth_token='+oauth_token);
76+
res.header('Content-Length', data.length);
77+
res.end(data);
78+
}
79+
});
80+
});
81+
82+
app.get('/auth/twitter/callback', function(req, res, next){
83+
// console.log(">>" + req.session.oauthRequestToken);
84+
// console.log(">>" + req.session.oauthRequestTokenSecret);
85+
// console.log(">>" + req.query.oauth_verifier);
86+
87+
if (req.query) {
88+
oa.getOAuthAccessToken(req.session.oauthRequestToken, req.session.oauthRequestTokenSecret, req.query.oauth_verifier,
89+
function(error, oauthAccessToken, oauthAccessTokenSecret, results){
90+
if (error){
91+
console.log(error);
92+
res.send("auth twitter callback: error");
93+
} else {
94+
req.session.oauthAccessToken = oauthAccessToken;
95+
req.session.oauthAccessTokenSecret = oauthAccessTokenSecret;
96+
97+
var client = new Twitter({
98+
consumer_key: configData.twitter.consumer_key,
99+
consumer_secret: configData.twitter.consumer_secret,
100+
access_token_key: oauthAccessToken,
101+
access_token_secret: oauthAccessTokenSecret
102+
});
103+
104+
var randomName = temp.path({suffix: '.png'}),
105+
imgPath = 'images' + randomName;
106+
107+
drawFromCss(req.session.cssData, imgPath, function() {
108+
// Tweet message and drawing
109+
var data = require('fs').readFileSync(imgPath);
110+
client.post('media/upload', {media: data}, function(error, media, response){
111+
if (!error) {
112+
// If successful, a media object will be returned.
113+
var status = {
114+
status: req.session.cssData.text,
115+
media_ids: media.media_id_string // Pass the media id string
116+
}
117+
client.post('statuses/update', status, function(error, tweet, response){
118+
if (!error) {
119+
// Success
120+
console.log(tweet);
121+
res.redirect('/')
122+
}
123+
fs.unlinkSync(imgPath);
124+
});
125+
} else {
126+
console.log(error);
127+
fs.unlinkSync(imgPath);
128+
}
129+
});
130+
});
131+
}
132+
}
133+
);
134+
} else
135+
next(new Error("auth twitter callback: error"))
136+
});
137+
138+
app.listen(3000, '127.0.0.1', function(){
139+
console.log("Express server listening on port %d in %s mode", 3000, app.settings.env);
140+
});
141+
142+
/**
143+
* Redux helper functions
144+
*/
145+
function handleRender(req, res) {
146+
// Create a new Redux store instance
147+
const store = redux.createStore(undoable(reducer, {
148+
initTypes: ['@@redux/SET_INITIAL_STATE', '@@SET_INITIAL_STATE'], // history will be (re)set upon init action type
149+
debug: true
150+
}));
151+
152+
//Dispatch initial state
153+
store.dispatch({
154+
type: 'SET_INITIAL_STATE',
155+
state: {}
156+
});
157+
158+
// Render the component to a string
159+
const html = renderToString(
160+
<Provider store={store}>
161+
<App />
162+
</Provider>
163+
);
164+
165+
// Grab the initial state from our Redux store
166+
const initialState = store.getState();
167+
168+
// Send the rendered page back to the client
169+
res.send(renderFullPage(html, initialState));
170+
}
171+
172+
function renderFullPage(html, initialState) {
173+
return `
174+
<!DOCTYPE html>
175+
<html>
176+
<head>
177+
<title>Pixel Art to CSS</title>
178+
<link rel="stylesheet" type="text/css" href="main.css">
179+
</head>
180+
<body>
181+
<header class="grid">
182+
<div class="col-2-3">
183+
<h1>Pixel Art to CSS</h1>
184+
</div>
185+
<div class="credits-wrapper col-1-3">
186+
<div>
187+
<h2>by <a target="_blank" href="http://www.jvrpath.com/">jvalen</a></h2>
188+
<iframe src="https://ghbtns.com/github-btn.html?user=jvalen&repo=pixel-art-react&type=star&count=true&size=large" frameborder="0" scrolling="0" width="120px" height="30px"></iframe>
189+
</div>
190+
</div>
191+
</header>
192+
<div id="app">${html}</div>
193+
<script type="text/javascript" src="https://code.jquery.com/jquery-2.1.4.min.js"></script>
194+
<script src="bundle.js"></script>
195+
</body>
196+
</html>
197+
`
198+
}
199+
200+
/**
201+
* Draw image with CSS data
202+
*/
203+
function drawFromCss(data, path, callback){
204+
var width = data.cols * data.pixelSize,
205+
height = data.rows * data.pixelSize,
206+
opacity = 0;
207+
208+
data.boxShadow = data.boxShadow.map(function(elem){
209+
return (
210+
{
211+
x: elem[0],
212+
y:elem[1],
213+
color:elem[3]}
214+
)
215+
}
216+
);
217+
218+
let gmImg = gm(width, height, '#000000' + ('0' + Math.round( (1 - opacity) * 255 ).toString(16)).slice(-2));
219+
220+
for (var i = 0; i < data.boxShadow.length; i++) {
221+
var aux = data.boxShadow[i];
222+
gmImg.fill(aux.color).drawRectangle(
223+
aux.x - data.pixelSize, aux.y - data.pixelSize, aux.x, aux.y
224+
);
225+
}
226+
227+
gmImg.write(
228+
path,
229+
function (err) {
230+
if (err) console.log(err);
231+
callback();
232+
}
233+
);
234+
}

dist/index.html

+5-2
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,14 @@
99
<h1>Pixel Art to CSS</h1>
1010
</div>
1111
<div class="credits-wrapper col-1-3">
12-
<h2>by <a target="_blank" href="http://www.jvrpath.com/">jvalen</a></h2>
13-
<iframe src="https://ghbtns.com/github-btn.html?user=jvalen&repo=pixel-art-react&type=star&count=true&size=large" frameborder="0" scrolling="0" width="120px" height="30px"></iframe>
12+
<div>
13+
<h2>by <a target="_blank" href="http://www.jvrpath.com/">jvalen</a></h2>
14+
<iframe src="https://ghbtns.com/github-btn.html?user=jvalen&repo=pixel-art-react&type=star&count=true&size=large" frameborder="0" scrolling="0" width="120px" height="30px"></iframe>
15+
</div>
1416
</div>
1517
</header>
1618
<div id="app"></div>
19+
<script type="text/javascript" src="https://code.jquery.com/jquery-2.1.4.min.js"></script>
1720
<script src="bundle.js"></script>
1821
</body>
1922
</html>

dist/main.css

+25-2
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,12 @@
5757
content: "\f12d";
5858
}
5959

60+
.fa-twitter:before {
61+
content: "\f099";
62+
padding-right: 1em;
63+
color: "#BBBBBB";
64+
}
65+
6066
.fa.fa-eyedropper, .fa.fa-paint-brush, .fa.fa-eraser {
6167
text-align: center;
6268
font-size: 2em;
@@ -88,6 +94,10 @@
8894
.col-1-4 {
8995
width: 25%;
9096
}
97+
98+
.col-3-4 {
99+
width: 75%;
100+
}
91101
.col-1-8 {
92102
width: 12.5%;
93103
}
@@ -214,13 +224,21 @@ button.red:active, button.red.active, .button.red:active, .button.red.active {
214224
width: 100%;
215225
}
216226

227+
.twitter-button {
228+
margin: 0 auto;
229+
display: table;
230+
}
231+
217232
h1 {
218233
font-size: 2em;
219234
}
220235

221236
h2 {
222237
font-size: 0.8em;
223238
padding-right: 1em;
239+
display: inline;
240+
position: relative;
241+
top: -0.6em;
224242
}
225243

226244
h3 {
@@ -256,7 +274,7 @@ body {
256274
}
257275

258276
@media only screen and (min-width: 1200px) {
259-
button {
277+
button, .button {
260278
font-size: 2em;
261279
}
262280

@@ -267,10 +285,15 @@ body {
267285
h1 {
268286
font-size: 3em;
269287
}
288+
289+
.credits-wrapper {
290+
top: 1.4em;
291+
position: relative;
292+
}
270293
}
271294

272295
@media only screen and (max-width: 794px) {
273-
button {
296+
button, .button {
274297
font-size: 0.8em;
275298
}
276299

images/tmp/README.md

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Folder where the images to share in Twitter are created temporarily.

package.json

+25-3
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@
66
"scripts": {
77
"test": "mocha --compilers js:babel-core/register --require ./test/test_helper.js 'test/**/*.@(js|jsx)'",
88
"test:watch": "npm run test -- --watch",
9-
"build": "webpack --progress --colors"
9+
"development": "webpack-dev-server",
10+
"deploy": "NODE_ENV=production webpack -p --config webpack.production.config.js",
11+
"server": "node server"
1012
},
1113
"keywords": [],
1214
"author": "Javier Valencia Romero",
@@ -22,7 +24,12 @@
2224
"mocha": "^2.3.4",
2325
"react-hot-loader": "^1.3.0",
2426
"webpack": "^1.12.9",
25-
"webpack-dev-server": "^1.14.0"
27+
"webpack-dev-server": "^1.14.0",
28+
"babel-plugin-react-transform": "^1.1.0",
29+
"babel-runtime": "^5.8.20",
30+
"react-transform-hmr": "^1.0.0",
31+
"webpack-dev-middleware": "^1.2.0",
32+
"webpack-hot-middleware": "^2.2.0"
2633
},
2734
"babel": {
2835
"presets": [
@@ -31,13 +38,28 @@
3138
]
3239
},
3340
"dependencies": {
41+
"babel": "^5.8.21",
42+
"babel-register": "^6.3.13",
43+
"body-parser": "^1.14.2",
44+
"cookie-parser": "^1.4.0",
45+
"cors": "^2.7.1",
46+
"express": "^4.13.1",
47+
"express-session": "^1.11.3",
48+
"gm": "^1.21.1",
3449
"immutable": "^3.7.5",
50+
"jade": ">= 0.0.1",
51+
"js-cookie": "^2.0.4",
52+
"node-localstorage": "^0.6.0",
53+
"oauth": "^0.9.14",
54+
"qs": "^4.0.0",
3555
"react": "^0.14.3",
3656
"react-color": "^1.3.0",
3757
"react-dom": "^0.14.3",
3858
"react-modal": "^0.6.1",
3959
"react-redux": "^4.0.0",
4060
"redux": "^3.0.4",
41-
"redux-undo": "^0.5.0"
61+
"redux-undo": "^0.5.0",
62+
"temp": "^0.8.3",
63+
"twitter": "^1.2.5"
4264
}
4365
}

server.js

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
require('babel-core/register')
2+
require('./app')

0 commit comments

Comments
 (0)