Skip to content

Commit 3d7c2aa

Browse files
Merge pull request #42 from cloudinary/react-sample
create react sample application
2 parents b4b862d + 2fa0ca6 commit 3d7c2aa

36 files changed

+19440
-1
lines changed

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,4 +41,5 @@ lib/
4141
dist/
4242
*-compiled.js*
4343
storybook-static/
44-
.storybook/
44+
.storybook/
45+
.idea/

samples/photo_album/.editorconfig

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# http://editorconfig.org
2+
3+
root = true
4+
5+
[*]
6+
charset = utf-8
7+
indent_style = space
8+
indent_size = 4
9+
end_of_line = lf
10+
insert_final_newline = true
11+
trim_trailing_whitespace = true
12+
13+
[*.md]
14+
insert_final_newline = false
15+
trim_trailing_whitespace = false

samples/photo_album/.gitignore

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# See https://help.github.com/ignore-files/ for more about ignoring files.
2+
3+
# dependencies
4+
/node_modules
5+
6+
# testing
7+
/coverage
8+
9+
# production
10+
/build
11+
12+
# misc
13+
.DS_Store
14+
.env.local
15+
.env.development.local
16+
.env.test.local
17+
.env.production.local
18+
/.idea
19+
/src/config/config.js
20+
21+
npm-debug.log*
22+
yarn-debug.log*
23+
yarn-error.log*

samples/photo_album/README.md

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
Cloudinary React Photo Album Sample
2+
=======================================
3+
4+
This sample project shows:
5+
6+
1. How to use the Cloudinary React components.
7+
2. How to upload files to Cloudinary in an unsigned manner, using an upload preset.
8+
3. How to use the dynamic list resource in order to maintain a short list of resources aggregated by tags.
9+
4. How to delete an image uploaded from the browser with an unsigned upload. You can find additional details in this [knowledge base article](https://support.cloudinary.com/hc/en-us/articles/202521132-How-to-delete-an-image-from-the-client-side-). Don't forget to set the `Return delete token` setting of your unsigned upload preset to `true`.
10+
11+
## Configuration ##
12+
13+
There are 2 settings you need to change for this demo to work. Copy or rename `src/config/config.js.sample` to `src/config/config.js` and edit the following:
14+
15+
1. **cloud_name** - Should be change to the cloud name you received when you registered for a Cloudinary account.
16+
2. **upload_preset** - You should first "Enable unsigned uploads" in the ["Upload Settings"](https://cloudinary.com/console/settings/upload) of your Cloudinary console and assign the resulting preset name to that field. Note, you may want to tweak and modify the upload preset's parameters.
17+
3. Additionally, in your Cloudinary console in the ["Security Settings"](https://cloudinary.com/console/settings/security) section you should uncheck the "list" item.
18+
19+
## Setup ##
20+
21+
Run `npm install` to install the required dependencies for this module.
22+
23+
## Running ##
24+
25+
Run `npm start` to start the server and automatically open a browser and navigate to the application's url.
26+
27+
The application is deplyoed at http://localhost:3000/
28+
29+
## Internals ##
30+
This sample is using [Webpack](https://webpack.github.io) for bundling and serving the application.
31+
32+
### Sample main components ###
33+
34+
#### Routing ####
35+
36+
The application has 2 routes:
37+
38+
* **/photos** - Presents a list of images tagged by `myphotoalbum`
39+
* **/photos/new** - Presents an upload control that allows uploading multiple files by a file input or drag-and-grop.
40+
Uploads have a dynamic progress bar. In addition it displays the details of successful uploads.
41+
42+
The default route is set to `/photos`.
43+
44+
#### Main Components ####
45+
> Photo list
46+
* [App.js](src/components/App.js) the application root component. Fetches the displayed list of images.
47+
* [PhotoList.js](src/components/PhotoList.js) displays the list of photos that was set to the state.
48+
* [Photo.js](src/components/Photo.js) displays a single image.
49+
* [PhotoThumbnail.js](src/components/PhotoThumbnails.js) displays the image transformations.
50+
51+
> Photo Upload
52+
* [PhotosUploader.js](src/components/PhotosUploader.js) displays the upload control and lists the properties of the uploaded images once upload completes successfully.
53+
54+
**Important observations**:
55+
* This implementation shows usage of both cloudinary file upload and a react upload control.
56+
* Changes to the title field are sent in the upload request. This is meant to illustrate the possibility of attaching extra meta-data to each upload image.
57+
* The upload control uses the `upload_preset` we configured in Configuration step. This uses the settings defined on Cloudinary side to process the uploaded file.
58+
59+
### Unsigned Upload ###
60+
61+
In order to add images to our photo album that would later be retrievable from the Cloudinary service we must select a tag which will serve as our source for the list. In this case `myphotoalbum`.
62+
While this tag can be set in the upload preset and remain hidden from the client side, in this sample we included it in the request itself to make this sample work without further configuration steps.
63+
64+
### List Resource ###
65+
66+
Cloudinary supports a JSON list resource.
67+
This list represents all resources marked with a specific tag during upload (or later through other APIs).
68+
Whenever a new resource is uploaded with a tag, or an existing resource already tagged is deleted then the list is recalculated.
69+
This enables you to group a list of resources which can be retrieved by a single query. The size of the list is currently limited to 100 entires.
70+
[Learn more](http://cloudinary.com/documentation/image_transformations#client_side_resource_lists)

samples/photo_album/config/env.js

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
'use strict';
2+
3+
const fs = require('fs');
4+
const path = require('path');
5+
const paths = require('./paths');
6+
7+
// Make sure that including paths.js after env.js will read .env variables.
8+
delete require.cache[require.resolve('./paths')];
9+
10+
const NODE_ENV = process.env.NODE_ENV;
11+
if (!NODE_ENV) {
12+
throw new Error(
13+
'The NODE_ENV environment variable is required but was not specified.'
14+
);
15+
}
16+
17+
// https://github.com/bkeepers/dotenv#what-other-env-files-can-i-use
18+
var dotenvFiles = [
19+
`${paths.dotenv}.${NODE_ENV}.local`,
20+
`${paths.dotenv}.${NODE_ENV}`,
21+
// Don't include `.env.local` for `test` environment
22+
// since normally you expect tests to produce the same
23+
// results for everyone
24+
NODE_ENV !== 'test' && `${paths.dotenv}.local`,
25+
paths.dotenv,
26+
].filter(Boolean);
27+
28+
// Load environment variables from .env* files. Suppress warnings using silent
29+
// if this file is missing. dotenv will never modify any environment variables
30+
// that have already been set.
31+
// https://github.com/motdotla/dotenv
32+
dotenvFiles.forEach(dotenvFile => {
33+
if (fs.existsSync(dotenvFile)) {
34+
require('dotenv').config({
35+
path: dotenvFile,
36+
});
37+
}
38+
});
39+
40+
// We support resolving modules according to `NODE_PATH`.
41+
// This lets you use absolute paths in imports inside large monorepos:
42+
// https://github.com/facebookincubator/create-react-app/issues/253.
43+
// It works similar to `NODE_PATH` in Node itself:
44+
// https://nodejs.org/api/modules.html#modules_loading_from_the_global_folders
45+
// Note that unlike in Node, only *relative* paths from `NODE_PATH` are honored.
46+
// Otherwise, we risk importing Node.js core modules into an app instead of Webpack shims.
47+
// https://github.com/facebookincubator/create-react-app/issues/1023#issuecomment-265344421
48+
// We also resolve them to make sure all tools using them work consistently.
49+
const appDirectory = fs.realpathSync(process.cwd());
50+
process.env.NODE_PATH = (process.env.NODE_PATH || '')
51+
.split(path.delimiter)
52+
.filter(folder => folder && !path.isAbsolute(folder))
53+
.map(folder => path.resolve(appDirectory, folder))
54+
.join(path.delimiter);
55+
56+
// Grab NODE_ENV and REACT_APP_* environment variables and prepare them to be
57+
// injected into the application via DefinePlugin in Webpack configuration.
58+
const REACT_APP = /^REACT_APP_/i;
59+
60+
function getClientEnvironment(publicUrl) {
61+
const raw = Object.keys(process.env)
62+
.filter(key => REACT_APP.test(key))
63+
.reduce(
64+
(env, key) => {
65+
env[key] = process.env[key];
66+
return env;
67+
},
68+
{
69+
// Useful for determining whether we’re running in production mode.
70+
// Most importantly, it switches React into the correct mode.
71+
NODE_ENV: process.env.NODE_ENV || 'development',
72+
// Useful for resolving the correct path to static assets in `public`.
73+
// For example, <img src={process.env.PUBLIC_URL + '/img/logo.png'} />.
74+
// This should only be used as an escape hatch. Normally you would put
75+
// images into the `src` and `import` them in code to get their paths.
76+
PUBLIC_URL: publicUrl,
77+
}
78+
);
79+
// Stringify all values so we can feed into Webpack DefinePlugin
80+
const stringified = {
81+
'process.env': Object.keys(raw).reduce((env, key) => {
82+
env[key] = JSON.stringify(raw[key]);
83+
return env;
84+
}, {}),
85+
};
86+
87+
return { raw, stringified };
88+
}
89+
90+
module.exports = getClientEnvironment;
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
'use strict';
2+
3+
// This is a custom Jest transformer turning style imports into empty objects.
4+
// http://facebook.github.io/jest/docs/tutorial-webpack.html
5+
6+
module.exports = {
7+
process() {
8+
return 'module.exports = {};';
9+
},
10+
getCacheKey() {
11+
// The output is always the same.
12+
return 'cssTransform';
13+
},
14+
};
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
'use strict';
2+
3+
const path = require('path');
4+
5+
// This is a custom Jest transformer turning file imports into filenames.
6+
// http://facebook.github.io/jest/docs/tutorial-webpack.html
7+
8+
module.exports = {
9+
process(src, filename) {
10+
return `module.exports = ${JSON.stringify(path.basename(filename))};`;
11+
},
12+
};
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
'use strict';
2+
3+
const path = require('path');
4+
const fs = require('fs');
5+
const url = require('url');
6+
7+
// Make sure any symlinks in the project folder are resolved:
8+
// https://github.com/facebookincubator/create-react-app/issues/637
9+
const appDirectory = fs.realpathSync(process.cwd());
10+
const resolveApp = relativePath => path.resolve(appDirectory, relativePath);
11+
12+
const envPublicUrl = process.env.PUBLIC_URL;
13+
14+
function ensureSlash(path, needsSlash) {
15+
const hasSlash = path.endsWith('/');
16+
if (hasSlash && !needsSlash) {
17+
return path.substr(path, path.length - 1);
18+
} else if (!hasSlash && needsSlash) {
19+
return `${path}/`;
20+
} else {
21+
return path;
22+
}
23+
}
24+
25+
const getPublicUrl = appPackageJson =>
26+
envPublicUrl || require(appPackageJson).homepage;
27+
28+
// We use `PUBLIC_URL` environment variable or "homepage" field to infer
29+
// "public path" at which the app is served.
30+
// Webpack needs to know it to put the right <script> hrefs into HTML even in
31+
// single-page apps that may serve index.html for nested URLs like /todos/42.
32+
// We can't use a relative path in HTML because we don't want to load something
33+
// like /todos/42/static/js/bundle.7289d.js. We have to know the root.
34+
function getServedPath(appPackageJson) {
35+
const publicUrl = getPublicUrl(appPackageJson);
36+
const servedUrl =
37+
envPublicUrl || (publicUrl ? url.parse(publicUrl).pathname : '/');
38+
return ensureSlash(servedUrl, true);
39+
}
40+
41+
// config after eject: we're in ./config/
42+
module.exports = {
43+
dotenv: resolveApp('.env'),
44+
appBuild: resolveApp('build'),
45+
appPublic: resolveApp('public'),
46+
appHtml: resolveApp('public/index.html'),
47+
appIndexJs: resolveApp('src/index.js'),
48+
appPackageJson: resolveApp('package.json'),
49+
appSrc: resolveApp('src'),
50+
yarnLockFile: resolveApp('yarn.lock'),
51+
testsSetup: resolveApp('src/setupTests.js'),
52+
appNodeModules: resolveApp('node_modules'),
53+
publicUrl: getPublicUrl(resolveApp('package.json')),
54+
servedPath: getServedPath(resolveApp('package.json')),
55+
};
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
'use strict';
2+
3+
if (typeof Promise === 'undefined') {
4+
// Rejection tracking prevents a common issue where React gets into an
5+
// inconsistent state due to an error, but it gets swallowed by a Promise,
6+
// and the user has no idea what causes React's erratic future behavior.
7+
require('promise/lib/rejection-tracking').enable();
8+
window.Promise = require('promise/lib/es6-extensions.js');
9+
}
10+
11+
// fetch() polyfill for making API calls.
12+
require('whatwg-fetch');
13+
14+
// Object.assign() is commonly used with React.
15+
// It will use the native implementation if it's present and isn't buggy.
16+
Object.assign = require('object-assign');
17+
18+
// In tests, polyfill requestAnimationFrame since jsdom doesn't provide it yet.
19+
// We don't polyfill it in the browser--this is user's responsibility.
20+
if (process.env.NODE_ENV === 'test') {
21+
require('raf').polyfill(global);
22+
}

0 commit comments

Comments
 (0)