This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, your program's commands
might be different; for a GUI interface, you would use an "about box".
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU GPL, see
.
The GNU General Public License does not permit incorporating your program
into proprietary programs. If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with
the library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License. But first, please read
.
================================================
FILE: README.md
================================================

====================================
[](http://slack-relax.herokuapp.com/)
**IMPORTANT NOTE:** Relax isn't yet ready for production, stay tuned for releases, beta version will come soon
Support our work and help us make this the best open source CMS, be our [patreon](http://patreon.com/relax)!
Relax is a powerful new generation CMS on top of
[React](https://facebook.github.io/react/) and [Node.js](https://nodejs.org/en/)
which aims for a better way of building websites.
It features a live page builder based on components and a smart and easy way of
binding dynamic data to them.
We're currently working on releasing the beta version which should come up early 2016. If you want to collaborate in the meantime or just say anything join us at [Relax Slack](http://slack-relax.herokuapp.com/)
**You can check the demo [here](http://demo.getrelax.io/admin)**
Demo credentials:
- user: demo
- pass: demo
New design for beta release
------------
(taken from using version in master branch, demo is outdated as master isn't yet stable)




Installation
------------
### Dependencies
Relax uses [sharp](https://github.com/lovell/sharp) to resize images.
If you're using OS X, you'll need to install its libvips dependency via `brew install homebrew/science/vips`.
Full installation instructions are available [here](http://sharp.dimens.io/en/stable/install/).
You'll also need [MongoDB](https://www.mongodb.org/).
### How to Relax
Since we are yet to tag our first release, git clone this repository and run
`npm install` followed by `npm start`.
By default the application runs at port `8080`. Go ahead and visit
`http://localhost:8080/admin/init`, here you can setup the first user and you're ready to relax.
Configuration
-------------
To configure the application you can use a `.relaxrc` file. You can place it
next to the application, on any parent folder, in your `HOME` folder, etc.
You can find a sample with the default values [here](.relaxrc.sample).
Contributing
------------
### Build and start
#### Development
While in development it's convenient to keep your application running while
watching for changes, for that you can run `npm run dev`.
The application will automatically restart when needed and keep your bundles
up to date.
#### Production
To build your assets ready to go for production run `npm run build` and `npm start` to start the application.
License
-------
Relax is [GPL-3 licensed](LICENSE).
Troubleshooting
---------------
Please create [an issue](https://github.com/relax/relax/issues/new).
================================================
FILE: ROADMAP.md
================================================
Roadmap
=======
v1.0.0-Beta (Spring 2016)
-------------------------
Since the start of the project we've been through some major refactors in
order to achieve the desired stack and experience to finally have a first stable
release.
We've changed from a RESTful API into a GraphQL API, while also changing the data
architecture in the front end to include Redux.
Our first dashboard was also not designed at all because our focus was mainly on
the page builder. We're now finally tuning up the entire admin experience
so that we can make it as relaxed as possible.
Below you can find the major things that are still in the road we're going through to
reach the beta release, there are of course other minor issues that can be found
[here](https://github.com/relax/relax/milestones/1.0.0-Beta).
**What must be done:**
- [ ] Dashboard (*ongoing*) (#214)
- [ ] Settings
- [ ] Pages
- [ ] Menus
- [ ] Schemas
- [ ] Fonts
- [ ] Users
- [ ] Media
- [ ] Symbols
- [ ] Link Element
- [ ] Allow to navigate to another page
- [ ] Allow to go an absolute URL
- [ ] Allow anchoring (with sections)
- [ ] Forms
- [ ] Compose e-mail and set where the e-mail should be sent to
- [ ] Symbols Management
- [x] Manage through context menu (#208)
- [ ] Edit symbol content (#209)
- [ ] Delete symbol through context menu
- [ ] Page Templates (#133)
- [ ] Save page as template
- [ ] Use template as a schema *single* template
- [ ] Use template as a starting point to a page
- [ ] Input option types (#158)
- [ ] Schema entry *singles* (#127)
- [ ] Import/export database
- [ ] Export theme (#56)
- [ ] Export data
- [ ] Export all
- [ ] Users
- [ ] Recover password
- [ ] Change password
- [ ] Edit basic info
- [ ] Third party elements API (#55)
- [ ] Developer API Documentation (#53)
**If we have time:**
- [ ] Link forms to schemas (#124)
- [ ] User account activation
- [ ] User roles management
- [ ] Schema entry single override template (#128)
- [ ] Docker image (#213)
There are no optional issues, we really want to have everything as polished as
possible for the first release.
Feel free to create missing issues or discuss some of the features in the issues
section or with us over Slack. All help and feedback is welcome.
================================================
FILE: app.js
================================================
import 'babel-polyfill';
import mongoose from 'mongoose';
import app from './lib/server';
import config from './config';
import logger from './lib/server/logger';
import migrate from './lib/server/migrate';
// Connect mongoose
if (!config.db) {
throw new Error('Configuration to MongoDB required');
}
mongoose.connect(config.db.uri, config.db);
// Run migrations
migrate()
.then(() => {
// Start server
var server = app.listen(config.port, () => {
var port = server.address().port;
logger.debug('Listening at port', port);
});
})
.done();
================================================
FILE: config.js
================================================
var rc = require('rc');
module.exports = rc('relax', {
port: 8080,
devPort: 8181,
db: {
uri: 'mongodb://localhost/relax'
}
});
================================================
FILE: lib/client/admin.js
================================================
import 'babel-polyfill';
import routes from 'routers/admin';
import renderRoutes from './helpers/render-routes';
renderRoutes(routes);
================================================
FILE: lib/client/auth.js
================================================
import routes from 'routers/auth';
import renderRoutes from './helpers/render-routes';
renderRoutes(routes);
================================================
FILE: lib/client/helpers/render-routes.js
================================================
import configureStore from 'helpers/configure-store';
import createHistory from 'history/lib/createBrowserHistory';
import React from 'react';
import {render} from 'react-dom';
import {Provider} from 'react-redux';
import {reduxReactRouter, ReduxRouter} from 'redux-router';
export default function renderRoutes (routes) {
const state = window.__initialState;
state.router = undefined;
const store = configureStore(
reduxReactRouter({createHistory, routes}),
state
);
render(
,
document.getElementById('view')
);
}
================================================
FILE: lib/client/public.js
================================================
import routes from 'routers/public';
import renderRoutes from './helpers/render-routes';
renderRoutes(routes);
================================================
FILE: lib/server/graphql/authorize.js
================================================
export default function authorize (root) {
if (!root.user) {
throw new Error('Unauthorized');
}
}
================================================
FILE: lib/server/graphql/mutations/color/add.js
================================================
import {
GraphQLNonNull
} from 'graphql';
import authorize from '../../authorize';
import colorInputType from '../../types/color-input';
import colorType from '../../types/color';
import ColorModel from '../../../models/color';
export default {
type: colorType,
args: {
data: {
name: 'data',
type: new GraphQLNonNull(colorInputType)
}
},
async resolve (root, params) {
authorize(root);
const colorModel = new ColorModel(params.data);
const color = await colorModel.save();
if (!color) {
throw new Error('Error adding new color');
}
return color;
}
};
================================================
FILE: lib/server/graphql/mutations/color/duplicate.js
================================================
import {
GraphQLNonNull,
GraphQLID
} from 'graphql';
import authorize from '../../authorize';
import colorType from '../../types/color';
import ColorModel from '../../../models/color';
export default {
type: colorType,
args: {
id: {
name: 'id',
type: new GraphQLNonNull(GraphQLID)
}
},
async resolve (root, params) {
authorize(root);
const colorToDuplicate = await ColorModel.findById(params.id).select('-_id label value').exec();
if (!colorToDuplicate) {
throw new Error('Color to duplicate could not be found!');
}
const color = new ColorModel(colorToDuplicate.toJSON());
const newColor = await color.save();
if (!newColor) {
throw new Error('Error adding new duplicate color');
}
return newColor;
}
};
================================================
FILE: lib/server/graphql/mutations/color/index.js
================================================
import addColor from './add';
import duplicateColor from './duplicate';
import removeColor from './remove';
import updateColor from './update';
export default {
addColor,
removeColor,
updateColor,
duplicateColor
};
================================================
FILE: lib/server/graphql/mutations/color/remove.js
================================================
import getProjection from 'helpers/get-projection';
import {
GraphQLNonNull,
GraphQLID
} from 'graphql';
import authorize from '../../authorize';
import colorType from '../../types/color';
import ColorModel from '../../../models/color';
export default {
type: colorType,
args: {
id: {
name: 'id',
type: new GraphQLNonNull(GraphQLID)
}
},
async resolve (root, params, options) {
authorize(root);
const removedColor = await ColorModel
.findByIdAndRemove(params.id, {
select: getProjection(options.fieldASTs[0])
})
.exec();
if (!removedColor) {
throw new Error('Color not found');
}
return removedColor;
}
};
================================================
FILE: lib/server/graphql/mutations/color/update.js
================================================
import {
GraphQLNonNull
} from 'graphql';
import getProjection from 'helpers/get-projection';
import authorize from '../../authorize';
import colorInputType from '../../types/color-input';
import colorType from '../../types/color';
import ColorModel from '../../../models/color';
export default {
type: colorType,
args: {
data: {
name: 'data',
type: new GraphQLNonNull(colorInputType)
}
},
async resolve (root, params, options) {
authorize(root);
const resultColor = await ColorModel
.findByIdAndUpdate(params.data._id, params.data, {
upsert: true,
new: true,
select: getProjection(options.fieldASTs[0])
})
.exec();
if (!resultColor) {
throw new Error('Color not found');
}
return resultColor;
}
};
================================================
FILE: lib/server/graphql/mutations/draft/drop.js
================================================
import {
GraphQLNonNull,
GraphQLString
} from 'graphql';
import {Types} from 'mongoose';
import getProjection from 'helpers/get-projection';
import authorize from '../../authorize';
import draftType from '../../types/draft';
import DraftModel from '../../../models/draft';
import PageModel from '../../../models/page';
export default {
type: draftType,
args: {
id: {
name: 'id',
type: new GraphQLNonNull(GraphQLString)
}
},
async resolve (root, params, options) {
authorize(root);
const projection = getProjection(options.fieldASTs[0]);
const _id = new Types.ObjectId(params.id);
const _userId = root.user._id;
const page = await PageModel.findById(_id);
const data = {
data: page.data,
actions: [],
__v: page.__v
};
const resultDraft = await DraftModel
.findByIdAndUpdate(
{_id, _userId},
data,
{upsert: true, new: true}
)
.select(projection)
.exec();
if (!resultDraft) {
throw new Error('Draft not found');
}
return resultDraft;
}
};
================================================
FILE: lib/server/graphql/mutations/draft/index.js
================================================
import dropDraft from './drop';
import updateDraft from './update';
export default {
dropDraft,
updateDraft
};
================================================
FILE: lib/server/graphql/mutations/draft/update.js
================================================
import getProjection from 'helpers/get-projection';
import {
GraphQLNonNull,
GraphQLID
} from 'graphql';
import {Types} from 'mongoose';
import authorize from '../../authorize';
import draftInputType from '../../types/draft-input';
import draftType from '../../types/draft';
import DraftModel from '../../../models/draft';
export default {
type: draftType,
args: {
id: {
name: 'id',
type: new GraphQLNonNull(GraphQLID)
},
data: {
name: 'data',
type: new GraphQLNonNull(draftInputType)
}
},
resolve (root, params, options) {
authorize(root);
const projection = getProjection(options.fieldASTs[0]);
const _id = new Types.ObjectId(params.id);
const _userId = root.user._id;
const data = Object.assign({}, params.data, {
data: JSON.parse(params.data.data),
actions: JSON.parse(params.data.actions)
});
delete data._id;
return DraftModel
.findByIdAndUpdate(
{_id, _userId},
data,
{upsert: true, new: true}
)
.select(projection)
.exec()
.then((resultDraft) => {
if (!resultDraft) {
throw new Error('Draft not found');
}
return resultDraft;
});
}
};
================================================
FILE: lib/server/graphql/mutations/fonts/index.js
================================================
import removeCustomFont from './remove-custom';
import submitCustomFont from './submit-custom';
import uploadFont from './upload';
export default {
removeCustomFont,
submitCustomFont,
uploadFont
};
================================================
FILE: lib/server/graphql/mutations/fonts/remove-custom.js
================================================
import path from 'path';
import rmdir from 'rimraf';
import Q from 'q';
import {
GraphQLNonNull,
GraphQLString,
GraphQLObjectType
} from 'graphql';
import authorize from '../../authorize';
export default {
type: new GraphQLObjectType({
name: 'removeCustomFont',
fields: {
id: {type: new GraphQLNonNull(GraphQLString)}
}
}),
args: {
id: {
name: 'id',
type: new GraphQLNonNull(GraphQLString)
}
},
resolve (root, params) {
authorize(root);
const id = params.id;
const fontsFolder = path.join(__dirname, '../../../../..', 'public/fonts', id);
return Q
.nfcall(rmdir, fontsFolder)
.then(() => ({
id
}))
.catch(() => {
throw new Error('Error removing custom fonts folder');
});
}
};
================================================
FILE: lib/server/graphql/mutations/fonts/submit-custom.js
================================================
import forEach from 'lodash.foreach';
import fs from 'fs';
import mkdirp from 'mkdirp';
import mongoose from 'mongoose';
import path from 'path';
import Q from 'q';
import {
GraphQLNonNull,
GraphQLString,
GraphQLList
} from 'graphql';
import authorize from '../../authorize';
import customFontType from '../../types/custom-font';
import uploadedInputType from '../../types/uploaded-input';
export default {
type: customFontType,
args: {
name: {
name: 'name',
type: new GraphQLNonNull(GraphQLString)
},
files: {
name: 'files',
type: new GraphQLNonNull(new GraphQLList(uploadedInputType))
},
types: {
name: 'types',
type: new GraphQLNonNull(new GraphQLList(GraphQLString))
}
},
resolve (root, params) {
authorize(root);
const files = params.files;
const types = params.types;
const id = mongoose.Types.ObjectId().toString();
const rootFolder = path.join(__dirname, '../../../../..');
const fontsFolder = path.join(rootFolder, 'public/fonts', id);
return Q
.nfcall(mkdirp, fontsFolder)
.then(() => {
const promises = [];
forEach(files, (file) => {
promises.push(
Q.ninvoke(
fs,
'rename',
path.join(rootFolder, file.path),
path.join(fontsFolder, file.originalname)
)
);
});
return Q.all(promises);
})
.then(() => {
// map types to file
const map = {};
for (let a = 0; a < files.length; a++) {
map[types[a]] = files[a].originalname;
}
return {
family: params.name,
id,
files: map
};
})
.catch(() => {
throw new Error('Error submiting custom fonts');
});
}
};
================================================
FILE: lib/server/graphql/mutations/fonts/upload.js
================================================
import authorize from '../../authorize';
import uploadedType from '../../types/uploaded';
export default {
type: uploadedType,
resolve (root) {
authorize(root);
return root.file;
}
};
================================================
FILE: lib/server/graphql/mutations/index.js
================================================
import color from './color';
import draft from './draft';
import fonts from './fonts';
import media from './media';
import menu from './menu';
import page from './page';
import schema from './schemas';
import schemaEntry from './schema-entry';
import settings from './settings';
import style from './style';
import symbol from './symbol';
import tab from './tab';
import user from './user';
export default {
...color,
...draft,
...fonts,
...media,
...menu,
...page,
...schemaEntry,
...schema,
...settings,
...style,
...symbol,
...tab,
...user
};
================================================
FILE: lib/server/graphql/mutations/media/add.js
================================================
import createImageThumbnail from 'helpers/create-image-thumbnail';
import fileMimetype from 'helpers/file-mimetype';
import filesize from 'file-size';
import mongoose from 'mongoose';
import path from 'path';
import writeFile from 'helpers/write-file';
import {
GraphQLNonNull
} from 'graphql';
import {getMediaType} from 'helpers/mime-types';
import authorize from '../../authorize';
import mediaInputType from '../../types/media-input';
import mediaType from '../../types/media';
import MediaModel from '../../../models/media';
export default {
type: mediaType,
args: {
data: {
name: 'data',
type: new GraphQLNonNull(mediaInputType)
}
},
async resolve (root, params) {
authorize(root);
const mediaModel = {};
const id = mongoose.Types.ObjectId();
const idStr = id.toString();
let file = params.data.file;
const mimetype = fileMimetype(file);
const relativePath = path.join('media', idStr);
const filePath = path.join('.', 'public', relativePath);
file = await writeFile(file, filePath);
// Image Upload
if (getMediaType(mimetype) === 'image') {
const {thumbnailPath, metadata} = await createImageThumbnail(
file.destPath,
filePath,
{
width: 100,
height: 100,
quality: 100
}
);
Object.assign(mediaModel, {
dimension: {
width: metadata.width,
height: metadata.height
},
thumbnail: path.join(relativePath, thumbnailPath)
});
}
// Create and save a new `MediaModel`
const media = new MediaModel(Object.assign(mediaModel, {
_id: id,
name: file.filename,
fileName: file.filename,
type: mimetype,
size: filesize(file.size).human(),
filesize: file.size,
absoluteUrl: file.destPath,
url: path.join(relativePath, file.filename)
}));
const newMedia = await media.save();
if (!newMedia) {
throw new Error('Error adding new media');
}
return newMedia;
}
};
================================================
FILE: lib/server/graphql/mutations/media/index.js
================================================
import addMedia from './add';
import removeMedia, {removeMediaItem} from './remove';
export default {
addMedia,
removeMedia,
removeMediaItem
};
================================================
FILE: lib/server/graphql/mutations/media/remove.js
================================================
import getProjection from 'helpers/get-projection';
import path from 'path';
import rmdir from 'rimraf';
import {
GraphQLNonNull,
GraphQLID,
GraphQLList
} from 'graphql';
import {all, nfcall} from 'q';
import authorize from '../../authorize';
import mediaType from '../../types/media';
import MediaModel from '../../../models/media';
const mediaPath = './public/media';
export default {
type: new GraphQLList(mediaType),
args: {
ids: {
name: 'ids',
type: new GraphQLList(new GraphQLNonNull(GraphQLID))
}
},
async resolve (root, params) {
authorize(root);
const {ids} = params;
const promises = [];
ids.forEach((id) => {
if (id) {
promises.push(nfcall(rmdir, path.join(mediaPath, id)));
}
});
await all(promises);
const removedMedia = await MediaModel.remove({
_id: {
$in: ids
}
});
return removedMedia.result.ok && ids.map(_id => ({_id})) || [];
}
};
export const removeMediaItem = {
type: mediaType,
args: {
id: {
name: 'id',
type: new GraphQLNonNull(GraphQLID)
}
},
async resolve (root, params, options) {
authorize(root);
const {id} = params;
const projection = getProjection(options.fieldASTs[0]);
await nfcall(rmdir, path.join(mediaPath, id));
const removedMedia = await MediaModel
.findByIdAndRemove(id)
.select(projection)
.exec();
return removedMedia;
}
};
================================================
FILE: lib/server/graphql/mutations/menu/add.js
================================================
import {
GraphQLNonNull
} from 'graphql';
import authorize from '../../authorize';
import menuInputType from '../../types/menu-input';
import menuType from '../../types/menu';
import MenuModel from '../../../models/menu';
export default {
type: menuType,
args: {
data: {
name: 'data',
type: new GraphQLNonNull(menuInputType)
}
},
async resolve (root, params) {
authorize(root);
const data = Object.assign(
{
data: {}
},
params.data
);
const menuModel = new MenuModel(data);
const menu = await menuModel.save();
if (!menu) {
throw new Error('Error adding menu');
}
return menu;
}
};
================================================
FILE: lib/server/graphql/mutations/menu/index.js
================================================
import addMenu from './add';
import removeMenu from './remove';
import updateMenu from './update';
export default {
addMenu,
removeMenu,
updateMenu
};
================================================
FILE: lib/server/graphql/mutations/menu/remove.js
================================================
import {
GraphQLNonNull,
GraphQLID
} from 'graphql';
import authorize from '../../authorize';
import menuType from '../../types/menu';
import MenuModel from '../../../models/menu';
export default {
type: menuType,
args: {
id: {
name: 'id',
type: new GraphQLNonNull(GraphQLID)
}
},
resolve (root, params) {
authorize(root);
return MenuModel
.findByIdAndRemove(params.id)
.exec()
.then((removedMenu) => {
if (!removedMenu) {
throw new Error('Error removing menu');
}
return removedMenu;
});
}
};
================================================
FILE: lib/server/graphql/mutations/menu/update.js
================================================
import getProjection from 'helpers/get-projection';
import {
GraphQLNonNull
} from 'graphql';
import authorize from '../../authorize';
import menuInputType from '../../types/menu-input';
import menuType from '../../types/menu';
import MenuModel from '../../../models/menu';
export default {
type: menuType,
args: {
data: {
name: 'data',
type: new GraphQLNonNull(menuInputType)
}
},
resolve (root, params, options) {
authorize(root);
const projection = getProjection(options.fieldASTs[0]);
const menuChanges = Object.assign({}, params.data);
const id = params.data._id;
// data hidrate
if (params.data.data && typeof params.data.data === 'string') {
menuChanges.data = JSON.parse(params.data.data);
}
return MenuModel
.findByIdAndUpdate(
id,
menuChanges,
{upsert: true, new: true}
)
.select(projection)
.exec()
.then((resultMenu) => {
if (!resultMenu) {
throw new Error('Menu not found');
}
return resultMenu;
});
}
};
================================================
FILE: lib/server/graphql/mutations/page/add.js
================================================
import getUniqueSlug from 'helpers/get-unique-slug';
import {
GraphQLNonNull
} from 'graphql';
import authorize from '../../authorize';
import pageInputType from '../../types/page-input';
import pageType from '../../types/page';
import PageModel from '../../../models/page';
export default {
type: pageType,
args: {
data: {
name: 'data',
type: new GraphQLNonNull(pageInputType)
}
},
async resolve (root, params) {
authorize(root);
const pageData = Object.assign({}, params.data);
// Generate slug if needed
if (!pageData.slug) {
pageData.slug = await getUniqueSlug(PageModel, pageData.title);
}
// Add user info
pageData.createdBy = root.user._id;
pageData.updatedBy = root.user._id;
const pageModel = new PageModel(pageData);
const page = await pageModel.save();
if (!page) {
throw new Error('Error creating page');
}
return page;
}
};
================================================
FILE: lib/server/graphql/mutations/page/duplicate.js
================================================
import {
GraphQLNonNull,
GraphQLString
} from 'graphql';
import authorize from '../../authorize';
import pageType from '../../types/page';
import PageModel from '../../../models/page';
function getUniqueSlug (slug, it) {
const sufix = it > 0 ? `-${it}` : '';
const resultSlug = `${slug}${sufix}`;
return PageModel
.findOne({slug: resultSlug})
.exec()
.then((response) => {
let result;
if (!response) {
result = resultSlug;
} else {
result = getUniqueSlug(slug, it + 1);
}
return result;
});
}
export default {
type: pageType,
args: {
data: {
name: 'data',
type: new GraphQLNonNull(GraphQLString)
}
},
async resolve (root, params) {
authorize(root);
let page = await PageModel.findById(params.data);
if (!page) {
throw new Error('Page to duplicate not found');
}
page = page.toJSON();
const slug = await getUniqueSlug(`${page.slug}-copy`, 0);
page.slug = slug;
page.title += ' (copy)';
page.state = 'draft';
delete page._id;
delete page.date;
delete page.actions;
const pageModel = new PageModel(page);
const newPage = await pageModel.save();
if (!newPage) {
throw new Error('Error duplicating page');
}
return newPage;
}
};
================================================
FILE: lib/server/graphql/mutations/page/index.js
================================================
import addPage from './add';
import duplicatePage from './duplicate';
import removePage from './remove';
import restorePage from './restore';
import updatePage from './update';
export default {
addPage,
duplicatePage,
removePage,
restorePage,
updatePage
};
================================================
FILE: lib/server/graphql/mutations/page/remove.js
================================================
import {
GraphQLNonNull,
GraphQLID
} from 'graphql';
import authorize from '../../authorize';
import pageType from '../../types/page';
import PageModel from '../../../models/page';
import RevisionModel from '../../../models/revision';
import TabModel from '../../../models/tab';
export default {
type: pageType,
args: {
id: {
name: 'id',
type: new GraphQLNonNull(GraphQLID)
}
},
async resolve (root, params) {
authorize(root);
const removedPage = await PageModel.findByIdAndRemove(params.id).exec();
if (!removedPage) {
throw new Error('Page not found');
}
await TabModel.find({'_id._id': params.id}).remove().exec();
await RevisionModel.find({'_id._id': params.id}).remove().exec();
return removedPage;
}
};
================================================
FILE: lib/server/graphql/mutations/page/restore.js
================================================
import {
GraphQLNonNull,
GraphQLID,
GraphQLInt
} from 'graphql';
import authorize from '../../authorize';
import pageType from '../../types/page';
import updatePageMutation from './update';
import RevisionModel from '../../../models/revision';
export default {
type: pageType,
args: {
pageId: {
name: 'pageId',
type: new GraphQLNonNull(GraphQLID)
},
version: {
name: 'version',
type: new GraphQLNonNull(GraphQLInt)
}
},
async resolve (root, params, options) {
authorize(root);
const {pageId: _id, version: __v} = params;
const revision = await RevisionModel.findOne({
'_id._id': _id,
'_id.__v': __v
}).exec();
return await updatePageMutation.resolve(root, {data: revision.doc}, options);
}
};
================================================
FILE: lib/server/graphql/mutations/page/update.js
================================================
import {
GraphQLNonNull
} from 'graphql';
import getProjection from 'helpers/get-projection';
import authorize from '../../authorize';
import pageInputType from '../../types/page-input';
import pageType from '../../types/page';
import PageModel from '../../../models/page';
import RevisionModel from '../../../models/revision';
export default {
type: pageType,
args: {
data: {
name: 'data',
type: new GraphQLNonNull(pageInputType)
}
},
async resolve (root, params, options) {
authorize(root);
const projection = getProjection(options.fieldASTs[0]);
const page = await PageModel.findById(params.data._id);
const revision = new RevisionModel({
_id: {
_id: page._id,
__v: page.__v
},
date: page.updatedDate,
user: page.updatedBy,
doc: page
});
await revision.save();
const pageChanges = Object.assign({}, params.data, {
__v: page.__v + 1,
updatedDate: new Date()
});
if (params.data.data && typeof params.data.data === 'string') {
pageChanges.data = JSON.parse(params.data.data);
}
const resultPage = await PageModel.findByIdAndUpdate(
params.data._id,
pageChanges,
{upsert: true, new: true}
).select(projection).exec();
if (!resultPage) {
throw new Error('Error updating page');
}
return resultPage;
}
};
================================================
FILE: lib/server/graphql/mutations/schema-entry/add.js
================================================
import getUniqueSlug from 'helpers/get-unique-slug';
import parseFields from 'helpers/parse-fields';
import {
GraphQLNonNull,
GraphQLID
} from 'graphql';
import authorize from '../../authorize';
import schemaEntryInputType from '../../types/schema-entry-input';
import schemaEntryModel from '../../../models/schema-entry';
import schemaEntryType from '../../types/schema-entry';
const parsableFields = ['data', 'properties'];
export default {
type: schemaEntryType,
args: {
schemaId: {
name: 'schemaId',
type: GraphQLID
},
data: {
name: 'data',
type: new GraphQLNonNull(schemaEntryInputType)
}
},
async resolve (root, params) {
authorize(root);
const Model = await schemaEntryModel(params.schemaId);
const data = parseFields(Object.assign({}, params.data), parsableFields);
// generate slug
if (!data.slug) {
data.slug = await getUniqueSlug(Model, data.title);
}
const schemaEntry = new Model(data);
const newSchemaEntry = await schemaEntry.save();
if (!newSchemaEntry) {
throw new Error('Error creating schema entry');
}
return newSchemaEntry;
}
};
================================================
FILE: lib/server/graphql/mutations/schema-entry/index.js
================================================
import addSchemaEntry from './add';
import removeSchemaEntry from './remove';
import restoreSchemaEntry from './restore';
import updateSchemaEntry from './update';
export default {
addSchemaEntry,
removeSchemaEntry,
restoreSchemaEntry,
updateSchemaEntry
};
================================================
FILE: lib/server/graphql/mutations/schema-entry/remove.js
================================================
import {
GraphQLNonNull,
GraphQLID
} from 'graphql';
import authorize from '../../authorize';
import schemaEntryModel from '../../../models/schema-entry';
import schemaEntryType from '../../types/schema-entry';
export default {
type: schemaEntryType,
args: {
schemaId: {
name: 'schemaId',
type: GraphQLID
},
id: {
name: 'id',
type: new GraphQLNonNull(GraphQLID)
}
},
async resolve (root, params) {
authorize(root);
const Model = await schemaEntryModel(params.schemaId);
const removedSchemaEntry = await Model.findByIdAndRemove(params.id).exec();
if (!removedSchemaEntry) {
throw new Error('Schema entry not found');
}
return removedSchemaEntry;
}
};
================================================
FILE: lib/server/graphql/mutations/schema-entry/restore.js
================================================
import {
GraphQLNonNull,
GraphQLID,
GraphQLInt
} from 'graphql';
import authorize from '../../authorize';
import schemaEntryType from '../../types/schema-entry';
import updateSchemaEntryMutation from './update';
import RevisionModel from '../../../models/revision';
export default {
type: schemaEntryType,
args: {
schemaId: {
name: 'schemaId',
type: GraphQLID
},
schemaEntryId: {
name: 'schemaEntryId',
type: new GraphQLNonNull(GraphQLID)
},
version: {
name: 'version',
type: new GraphQLNonNull(GraphQLInt)
}
},
async resolve (root, params, options) {
authorize(root);
const {schemaEntryId: _id, version: __v} = params;
const revision = await RevisionModel.findOne({
'_id._id': _id,
'_id.__v': __v
}).exec();
return await updateSchemaEntryMutation.resolve(
root,
{schemaId: params.schemaId, data: revision.doc},
options
);
}
};
================================================
FILE: lib/server/graphql/mutations/schema-entry/update.js
================================================
import getProjection from 'helpers/get-projection';
import parseFields from 'helpers/parse-fields';
import {
GraphQLNonNull,
GraphQLID
} from 'graphql';
import authorize from '../../authorize';
import schemaEntryInputType from '../../types/schema-entry-input';
import schemaEntryModel from '../../../models/schema-entry';
import schemaEntryType from '../../types/schema-entry';
import RevisionModel from '../../../models/revision';
const parsableFields = ['data', 'properties'];
export default {
type: schemaEntryType,
args: {
schemaId: {
name: 'schemaId',
type: GraphQLID
},
data: {
name: 'data',
type: new GraphQLNonNull(schemaEntryInputType)
}
},
async resolve (root, params, options) {
authorize(root);
const projection = getProjection(options.fieldASTs[0]);
const Model = await schemaEntryModel(params.schemaId);
const schemaEntry = await Model.findById(params.data._id);
const revision = new RevisionModel({
_id: {
_id: schemaEntry._id,
__v: schemaEntry.__v
},
date: schemaEntry.updatedDate,
user: schemaEntry.updatedBy,
doc: schemaEntry
});
await revision.save();
const schemaEntryChanges = parseFields(Object.assign({}, params.data, {
__v: schemaEntry.__v + 1,
updatedDate: new Date()
}), parsableFields);
const resultSchemaEntry = await Model.findByIdAndUpdate(
params.data._id,
schemaEntryChanges,
{upsert: true, new: true}
).select(projection).exec();
if (!resultSchemaEntry) {
throw new Error('Error updating schemaEntry');
}
return resultSchemaEntry;
}
};
================================================
FILE: lib/server/graphql/mutations/schemas/add.js
================================================
import getUniqueSlug from 'helpers/get-unique-slug';
import {
GraphQLNonNull
} from 'graphql';
import authorize from '../../authorize';
import schemaInputType from '../../types/schema-input';
import schemaType from '../../types/schema';
import SchemaModel from '../../../models/schema';
export default {
type: schemaType,
args: {
data: {
name: 'data',
type: new GraphQLNonNull(schemaInputType)
}
},
async resolve (root, params) {
authorize(root);
const data = Object.assign({}, params.data, {
properties: JSON.parse(params.data.properties)
});
// generate slug
if (!data.slug) {
data.slug = await getUniqueSlug(SchemaModel, data.title);
}
const schema = new SchemaModel(data);
const newSchema = await schema.save();
if (!newSchema) {
throw new Error('Error adding schema');
}
return newSchema;
}
};
================================================
FILE: lib/server/graphql/mutations/schemas/index.js
================================================
import addSchema from './add';
import removeSchema from './remove';
import restoreSchema from './restore';
import updateSchema from './update';
export default {
addSchema,
removeSchema,
restoreSchema,
updateSchema
};
================================================
FILE: lib/server/graphql/mutations/schemas/remove.js
================================================
import {
GraphQLNonNull,
GraphQLString
} from 'graphql';
import authorize from '../../authorize';
import schemaType from '../../types/schema';
import SchemaModel from '../../../models/schema';
export default {
type: schemaType,
args: {
id: {
name: 'id',
type: new GraphQLNonNull(GraphQLString)
}
},
async resolve (root, params) {
authorize(root);
const removedSchema = await SchemaModel.findByIdAndRemove(params.id);
if (!removedSchema) {
throw new Error('Schema not found');
}
return removedSchema;
}
};
================================================
FILE: lib/server/graphql/mutations/schemas/restore.js
================================================
import {
GraphQLNonNull,
GraphQLID,
GraphQLInt
} from 'graphql';
import authorize from '../../authorize';
import schemaType from '../../types/schema';
import updateSchemaMutation from './update';
import RevisionModel from '../../../models/revision';
export default {
type: schemaType,
args: {
schemaId: {
name: 'schemaId',
type: new GraphQLNonNull(GraphQLID)
},
version: {
name: 'version',
type: new GraphQLNonNull(GraphQLInt)
}
},
async resolve (root, params, options) {
authorize(root);
const {schemaId: _id, version: __v} = params;
const revision = await RevisionModel.findOne({
'_id._id': _id,
'_id.__v': __v
}).exec();
return await updateSchemaMutation.resolve(root, {data: revision.doc}, options);
}
};
================================================
FILE: lib/server/graphql/mutations/schemas/update.js
================================================
import {
GraphQLNonNull
} from 'graphql';
import getProjection from 'helpers/get-projection';
import authorize from '../../authorize';
import schemaInputType from '../../types/schema-input';
import schemaType from '../../types/schema';
import RevisionModel from '../../../models/revision';
import SchemaModel from '../../../models/schema';
export default {
type: schemaType,
args: {
data: {
name: 'data',
type: new GraphQLNonNull(schemaInputType)
}
},
async resolve (root, params, options) {
authorize(root);
const projection = getProjection(options.fieldASTs[0]);
const schema = await SchemaModel.findById(params.data._id);
const revision = new RevisionModel({
_id: {
_id: schema._id,
__v: schema.__v
},
date: schema.updatedDate,
user: schema.updatedBy,
doc: schema
});
await revision.save();
const schemaChanges = Object.assign({}, params.data, {
__v: schema.__v + 1,
updatedDate: new Date()
});
if (params.data.data && typeof params.data.data === 'string') {
schemaChanges.data = JSON.parse(params.data.data);
}
if (params.data.properties && typeof params.data.properties === 'string') {
schemaChanges.properties = JSON.parse(params.data.properties);
}
const resultSchema = await SchemaModel.findByIdAndUpdate(
params.data._id,
schemaChanges,
{upsert: true, new: true}
).select(projection).exec();
if (!resultSchema) {
throw new Error('Error updating schema');
}
return resultSchema;
}
};
================================================
FILE: lib/server/graphql/mutations/settings/index.js
================================================
import saveSettings from './save';
export default {
saveSettings
};
================================================
FILE: lib/server/graphql/mutations/settings/save.js
================================================
import forEach from 'lodash.foreach';
import getProjection from 'helpers/get-projection';
import Q from 'q';
import {
GraphQLNonNull,
GraphQLList
} from 'graphql';
import authorize from '../../authorize';
import settingInputType from '../../types/setting-input';
import settingType from '../../types/setting';
import SettingModel from '../../../models/setting';
export default {
type: new GraphQLList(settingType),
args: {
data: {
name: 'data',
type: new GraphQLNonNull(
new GraphQLList(settingInputType)
)
}
},
resolve (root, params, options) {
authorize(root);
const projection = getProjection(options.fieldASTs[0]);
const promises = [];
forEach(params.data, (setting) => {
promises.push(
SettingModel.findByIdAndUpdate(setting._id, setting, {upsert: true, new: true}).select(projection)
);
});
return Q
.all(promises)
.spread((...settings) => settings);
}
};
================================================
FILE: lib/server/graphql/mutations/style/add.js
================================================
import parseFields from 'helpers/parse-fields';
import {
GraphQLNonNull
} from 'graphql';
import authorize from '../../authorize';
import parsableFields from './parsable-fields';
import styleInputType from '../../types/style-input';
import styleType from '../../types/style';
import StyleModel from '../../../models/style';
export default {
type: styleType,
args: {
data: {
name: 'data',
type: new GraphQLNonNull(styleInputType)
}
},
async resolve (root, params) {
authorize(root);
const data = parseFields(params.data, parsableFields);
const styleModel = new StyleModel(data);
const style = await styleModel.save();
if (!style) {
throw new Error('Style not found');
}
return style;
}
};
================================================
FILE: lib/server/graphql/mutations/style/index.js
================================================
import addStyle from './add';
import removeStyle from './remove';
import updateStyle from './update';
export default {
addStyle,
removeStyle,
updateStyle
};
================================================
FILE: lib/server/graphql/mutations/style/parsable-fields.js
================================================
export default ['options', 'displayOptions'];
================================================
FILE: lib/server/graphql/mutations/style/remove.js
================================================
import getProjection from 'helpers/get-projection';
import {
GraphQLNonNull,
GraphQLID
} from 'graphql';
import authorize from '../../authorize';
import styleType from '../../types/style';
import StyleModel from '../../../models/style';
export default {
type: styleType,
args: {
_id: {
name: '_id',
type: new GraphQLNonNull(GraphQLID)
}
},
async resolve (root, params, options) {
authorize(root);
const removedStyle = await StyleModel
.findByIdAndRemove(params._id, {
select: getProjection(options.fieldASTs[0])
})
.exec();
if (!removedStyle) {
throw new Error('Style not found');
}
return removedStyle;
}
};
================================================
FILE: lib/server/graphql/mutations/style/update.js
================================================
import getProjection from 'helpers/get-projection';
import parseFields from 'helpers/parse-fields';
import {
GraphQLNonNull
} from 'graphql';
import authorize from '../../authorize';
import parsableFields from './parsable-fields';
import styleInputType from '../../types/style-input';
import styleType from '../../types/style';
import StyleModel from '../../../models/style';
export default {
type: styleType,
args: {
data: {
name: 'data',
type: new GraphQLNonNull(styleInputType)
}
},
async resolve (root, params, options) {
authorize(root);
const data = parseFields(params.data, parsableFields);
const resultStyle = await StyleModel
.findByIdAndUpdate(data._id, data, {
upsert: true,
new: true,
select: getProjection(options.fieldASTs[0])
})
.exec();
if (!resultStyle) {
throw new Error('Style not found');
}
return resultStyle;
}
};
================================================
FILE: lib/server/graphql/mutations/symbol/add.js
================================================
import {
GraphQLNonNull
} from 'graphql';
import authorize from '../../authorize';
import symbolInputType from '../../types/symbol-input';
import symbolType from '../../types/symbol';
import SymbolModel from '../../../models/symbol';
export default {
type: symbolType,
args: {
data: {
name: 'data',
type: new GraphQLNonNull(symbolInputType)
}
},
async resolve (root, params) {
authorize(root);
const data = Object.assign({}, params.data, {
data: JSON.parse(params.data.data)
});
const symbolModel = new SymbolModel(data);
const newSymbol = await symbolModel.save();
if (!newSymbol) {
throw new Error('Error adding symbol');
}
return newSymbol;
}
};
================================================
FILE: lib/server/graphql/mutations/symbol/index.js
================================================
import addSymbol from './add';
export default {
addSymbol
};
================================================
FILE: lib/server/graphql/mutations/tab/add.js
================================================
import {
GraphQLNonNull,
GraphQLString,
GraphQLID
} from 'graphql';
import authorize from '../../authorize';
import tabType from '../../types/tab';
import TabModel from '../../../models/tab';
export default {
type: tabType,
args: {
type: {
name: 'type',
type: new GraphQLNonNull(GraphQLString)
},
id: {
name: 'id',
type: new GraphQLNonNull(GraphQLID)
}
},
async resolve (root, params) {
authorize(root);
const _userId = root.user._id;
const item = params.id;
const type = params.type;
let tab = await TabModel.findOne({_userId, item}).exec();
if (!tab) {
const tabModel = new TabModel({
_userId,
type,
item
});
tab = await tabModel.save();
if (!tab) {
throw new Error('Error creating tab');
}
} else {
tab = null;
}
return tab;
}
};
================================================
FILE: lib/server/graphql/mutations/tab/index.js
================================================
import addTab from './add';
import removeTab from './remove';
export default {
removeTab,
addTab
};
================================================
FILE: lib/server/graphql/mutations/tab/remove.js
================================================
import getProjection from 'helpers/get-projection';
import {
GraphQLNonNull,
GraphQLID
} from 'graphql';
import authorize from '../../authorize';
import tabType from '../../types/tab';
import TabModel from '../../../models/tab';
export default {
type: tabType,
args: {
id: {
name: 'id',
type: new GraphQLNonNull(GraphQLID)
}
},
async resolve (root, params, options) {
authorize(root);
const removedTab = await TabModel
.findByIdAndRemove(params.id, {
select: getProjection(options.fieldASTs[0])
})
.exec();
if (!removedTab) {
throw new Error('Tab not found');
}
return removedTab;
}
};
================================================
FILE: lib/server/graphql/mutations/user/add.js
================================================
import {
GraphQLNonNull
} from 'graphql';
import authorize from '../../authorize';
import userInputType from '../../types/user-input';
import userType from '../../types/user';
import UserModel from '../../../models/user';
async function registerUser (user, password) {
return new Promise((resolve, reject) => {
UserModel.register(user, password, (err) => {
if (err) {
reject(err);
}
resolve();
});
});
}
export default {
type: userType,
args: {
data: {
name: 'data',
type: new GraphQLNonNull(userInputType)
}
},
async resolve (root, params) {
const {username, name, email, password} = params.data;
const user = new UserModel({
username,
name,
email
});
const count = await UserModel.count();
if (count > 0) {
authorize(root);
}
await registerUser(user, password);
return user;
}
};
================================================
FILE: lib/server/graphql/mutations/user/index.js
================================================
import addUser from './add';
import removeUser from './remove';
export default {
addUser,
removeUser
};
================================================
FILE: lib/server/graphql/mutations/user/remove.js
================================================
import {
GraphQLNonNull,
GraphQLID
} from 'graphql';
import authorize from '../../authorize';
import config from '../../../../../config';
import userType from '../../types/user';
import UserModel from '../../../models/user';
export default {
type: userType,
args: {
id: {
name: 'id',
type: new GraphQLNonNull(GraphQLID)
}
},
resolve (root, params) {
authorize(root);
if (config.demo) {
throw new Error('Remove user is disabled on the demo');
}
return UserModel
.findByIdAndRemove(params.id)
.exec()
.then((removedUser) => {
if (!removedUser) {
throw new Error('Error removing user');
}
return removedUser;
});
}
};
================================================
FILE: lib/server/graphql/queries/color/colors.js
================================================
import getProjection from 'helpers/get-projection';
import {
GraphQLList
} from 'graphql';
import colorType from '../../types/color';
import ColorModel from '../../../models/color';
export default {
type: new GraphQLList(colorType),
args: {},
resolve (root, params, options) {
const projection = getProjection(options.fieldASTs[0]);
return ColorModel.find().select(projection).exec();
}
};
================================================
FILE: lib/server/graphql/queries/color/index.js
================================================
import colors from './colors';
export default {
colors
};
================================================
FILE: lib/server/graphql/queries/draft/draft.js
================================================
import getProjection from 'helpers/get-projection';
import {
GraphQLID
} from 'graphql';
import {Types} from 'mongoose';
import authorize from '../../authorize';
import draftType from '../../types/draft';
import DraftModel from '../../../models/draft';
import PageModel from '../../../models/page';
export default {
type: draftType,
args: {
id: {
name: 'id',
type: GraphQLID
}
},
async resolve (root, params, options) {
authorize(root);
const projection = getProjection(options.fieldASTs[0]);
const _id = new Types.ObjectId(params.id);
const _userId = root.user._id;
let result = await DraftModel.findById({_id, _userId}).select(projection).exec();
if (!result) {
const page = await PageModel.findById(_id).exec();
const draft = new DraftModel({
_id: {
_id,
_userId
},
__v: page.__v,
data: page.data
});
result = await draft.save();
}
return result;
}
};
================================================
FILE: lib/server/graphql/queries/draft/index.js
================================================
import draft from './draft';
export default {
draft
};
================================================
FILE: lib/server/graphql/queries/generators/schema-list-count.js
================================================
import authorize from '../../authorize';
import countType from '../../types/count';
import schemaEntryModel from '../../../models/schema-entry';
export default (type, schema) => ({
type: countType,
args: {},
async resolve (root) {
authorize(root);
const Model = schemaEntryModel(schema);
const count = await Model.count({}).exec();
return {count};
}
});
================================================
FILE: lib/server/graphql/queries/generators/schema-list.js
================================================
import getProjection from 'helpers/get-projection';
import {
GraphQLList
} from 'graphql';
import authorize from '../../authorize';
import schemaEntryModel from '../../../models/schema-entry';
import {paginationQueryArgs, paginateQuery, searchQuery} from '../../query-pagination';
export default (type, schema) => ({
type: new GraphQLList(type),
args: {
...paginationQueryArgs
},
async resolve (root, params, options) {
authorize(root);
const Model = schemaEntryModel(schema);
const projection = getProjection(options.fieldASTs[0]);
const query = Model.find(searchQuery({}, params));
paginateQuery(query, params);
return await query.select(projection).exec();
}
});
================================================
FILE: lib/server/graphql/queries/index.js
================================================
import color from './color';
import draft from './draft';
import media from './media';
import menu from './menu';
import page from './page';
import revision from './revision';
import schema from './schemas';
import schemaEntry from './schema-entry';
import settings from './settings';
import style from './style';
import symbol from './symbol';
import tab from './tab';
import user from './user';
export default {
...color,
...draft,
...media,
...menu,
...page,
...revision,
...schemaEntry,
...schema,
...settings,
...style,
...symbol,
...tab,
...user
};
================================================
FILE: lib/server/graphql/queries/media/index.js
================================================
import mediaCount from './media-count';
import media, {mediaItem} from './media';
export default {
media,
mediaItem,
mediaCount
};
================================================
FILE: lib/server/graphql/queries/media/media-count.js
================================================
import {GraphQLInt} from 'graphql';
import authorize from '../../authorize';
import MediaModel from '../../../models/media';
export default {
type: GraphQLInt,
args: {},
async resolve (root) {
authorize(root);
return await MediaModel.count();
}
};
================================================
FILE: lib/server/graphql/queries/media/media.js
================================================
import getProjection from 'helpers/get-projection';
import {
GraphQLList,
GraphQLNonNull,
GraphQLID
} from 'graphql';
import authorize from '../../authorize';
import mediaType from '../../types/media';
import MediaModel from '../../../models/media';
import {paginationQueryArgs, paginateQuery, searchQuery} from '../../query-pagination';
export default {
type: new GraphQLList(mediaType),
args: {
...paginationQueryArgs
},
async resolve (root, params, options) {
authorize(root);
const projection = getProjection(options.fieldASTs[0]);
const query = MediaModel.find(searchQuery({}, params));
paginateQuery(query, params);
return query.select(projection).exec();
}
};
export const mediaItem = {
type: mediaType,
args: {
id: {
name: 'id',
type: new GraphQLNonNull(GraphQLID)
}
},
async resolve (root, params, options) {
const projection = getProjection(options.fieldASTs[0]);
const query = MediaModel.findById(params.id);
return query.select(projection).exec();
}
};
================================================
FILE: lib/server/graphql/queries/menu/index.js
================================================
import menus from './menus';
import menusCount from './menus-count';
import menu, {validateMenuSlug} from './menu';
export default {
menus,
menusCount,
menu,
validateMenuSlug
};
================================================
FILE: lib/server/graphql/queries/menu/menu.js
================================================
import getProjection from 'helpers/get-projection';
import {
GraphQLString,
GraphQLID,
GraphQLNonNull,
GraphQLBoolean
} from 'graphql';
import authorize from '../../authorize';
import menuType from '../../types/menu';
import MenuModel from '../../../models/menu';
export default {
type: menuType,
args: {
_id: {
name: '_id',
type: GraphQLID
}
},
async resolve (root, params, options) {
authorize(root);
const projection = getProjection(options.fieldASTs[0]);
return await MenuModel.findById(params._id).select(projection).exec();
}
};
export const validateMenuSlug = {
type: GraphQLBoolean,
args: {
slug: {
name: 'slug',
type: new GraphQLNonNull(GraphQLString)
},
menuId: {
name: 'menuId',
type: GraphQLID
}
},
async resolve (root, {slug, menuId}) {
authorize(root);
return await MenuModel.count({
slug,
_id: {
$ne: menuId
}
}) === 0;
}
};
================================================
FILE: lib/server/graphql/queries/menu/menus-count.js
================================================
import {GraphQLInt} from 'graphql';
import authorize from '../../authorize';
import MenuModel from '../../../models/menu';
export default {
type: GraphQLInt,
args: {},
async resolve (root) {
authorize(root);
return await MenuModel.count();
}
};
================================================
FILE: lib/server/graphql/queries/menu/menus.js
================================================
import getProjection from 'helpers/get-projection';
import {
GraphQLList
} from 'graphql';
import authorize from '../../authorize';
import menuType from '../../types/menu';
import MenuModel from '../../../models/menu';
import {paginationQueryArgs, paginateQuery, searchQuery} from '../../query-pagination';
export default {
type: new GraphQLList(menuType),
args: {
...paginationQueryArgs
},
resolve: (root, params, options) => {
authorize(root);
const projection = getProjection(options.fieldASTs[0]);
const query = MenuModel.find(searchQuery({}, params));
paginateQuery(query, params);
return query.select(projection).exec();
}
};
================================================
FILE: lib/server/graphql/queries/page/index.js
================================================
import pages from './pages';
import pagesCount from './pages-count';
import page, {validatePageSlug} from './page';
export default {
pages,
pagesCount,
page,
validatePageSlug
};
================================================
FILE: lib/server/graphql/queries/page/page.js
================================================
import getProjection from 'helpers/get-projection';
import {
GraphQLString,
GraphQLBoolean,
GraphQLNonNull,
GraphQLID
} from 'graphql';
import authorize from '../../authorize';
import pageType from '../../types/page';
import PageModel from '../../../models/page';
import SettingModel from '../../../models/setting';
export default {
type: pageType,
args: {
_id: {
name: '_id',
type: GraphQLID
},
slug: {
name: 'slug',
type: GraphQLString
}
},
async resolve (root, params, options) {
const projection = getProjection(options.fieldASTs[0]);
let result = false;
if (params.slug || params._id) {
result = await PageModel.findOne(params).select(projection).exec();
} else {
const frontpage = await SettingModel.findById('frontpage').exec();
if (!frontpage) {
throw new Error('Frontpage not defined');
}
result = await PageModel.findById(frontpage.value).select(projection).exec();
}
return result;
}
};
export const validatePageSlug = {
type: GraphQLBoolean,
args: {
slug: {
name: 'slug',
type: new GraphQLNonNull(GraphQLString)
},
pageId: {
name: 'pageId',
type: GraphQLID
}
},
async resolve (root, {slug, pageId}) {
authorize(root);
return await PageModel.count({
slug,
_id: {
$ne: pageId
}
}) === 0;
}
};
================================================
FILE: lib/server/graphql/queries/page/pages-count.js
================================================
import {GraphQLInt} from 'graphql';
import PageModel from '../../../models/page';
export default {
type: GraphQLInt,
args: {},
async resolve () {
const count = PageModel.count();
return count;
}
};
================================================
FILE: lib/server/graphql/queries/page/pages.js
================================================
import getProjection from 'helpers/get-projection';
import {
GraphQLList
} from 'graphql';
import authorize from '../../authorize';
import pageType from '../../types/page';
import PageModel from '../../../models/page';
import {paginationQueryArgs, paginateQuery, searchQuery} from '../../query-pagination';
export default {
type: new GraphQLList(pageType),
args: {
...paginationQueryArgs
},
resolve (root, params, options) {
authorize(root);
const projection = getProjection(options.fieldASTs[0]);
const query = PageModel.find(searchQuery({}, params));
paginateQuery(query, params);
return query.select(projection).exec();
}
};
================================================
FILE: lib/server/graphql/queries/revision/index.js
================================================
import revisions from './revisions';
export default {
revisions
};
================================================
FILE: lib/server/graphql/queries/revision/revisions.js
================================================
import getProjection from 'helpers/get-projection';
import {
GraphQLList,
GraphQLString
} from 'graphql';
import {Types} from 'mongoose';
import authorize from '../../authorize';
import revisionType from '../../types/revision';
import RevisionModel from '../../../models/revision';
export default {
type: new GraphQLList(revisionType),
args: {
id: {
name: 'id',
type: GraphQLString
}
},
async resolve (root, params, options) {
authorize(root);
const id = new Types.ObjectId(params.id);
const projection = getProjection(options.fieldASTs[0]);
return await RevisionModel
.find({
'_id._id': id
})
.sort({'_id.__v': -1})
.select(projection)
.exec();
}
};
================================================
FILE: lib/server/graphql/queries/schema-entry/index.js
================================================
import schemaList from './schema-list';
import schemaListCount from './schema-list-count';
import schemaEntry, {validateSchemaEntrySlug} from './schema-entry';
export default {
schemaEntry,
validateSchemaEntrySlug,
schemaList,
schemaListCount
};
================================================
FILE: lib/server/graphql/queries/schema-entry/schema-entry.js
================================================
import getProjection from 'helpers/get-projection';
import {
GraphQLString,
GraphQLBoolean,
GraphQLNonNull,
GraphQLID
} from 'graphql';
import authorize from '../../authorize';
import schemaEntryModel from '../../../models/schema-entry';
import schemaEntryType from '../../types/schema-entry';
export default {
type: schemaEntryType,
args: {
id: {
name: 'id',
type: GraphQLID
},
schemaId: {
name: 'schemaId',
type: GraphQLID
}
},
async resolve (root, {schemaId, id}, options) {
const Model = await schemaEntryModel(schemaId);
const projection = getProjection(options.fieldASTs[0]);
return await Model.findById(id).select(projection).exec();
}
};
export const validateSchemaEntrySlug = {
type: GraphQLBoolean,
args: {
slug: {
name: 'slug',
type: new GraphQLNonNull(GraphQLString)
},
schemaId: {
name: 'schemaId',
type: GraphQLID
},
schemaEntryId: {
name: 'schemaEntryId',
type: GraphQLID
}
},
async resolve (root, {slug, schemaId, schemaEntryId}) {
authorize(root);
const Model = await schemaEntryModel(schemaId);
return await Model.count({
slug,
_id: {
$ne: schemaEntryId
}
}) === 0;
}
};
================================================
FILE: lib/server/graphql/queries/schema-entry/schema-list-count.js
================================================
import {
GraphQLID,
GraphQLInt
} from 'graphql';
import authorize from '../../authorize';
import schemaEntryModel from '../../../models/schema-entry';
export default {
type: GraphQLInt,
args: {
schemaId: {
name: 'schemaId',
type: GraphQLID
}
},
async resolve (root, params) {
authorize(root);
const Model = await schemaEntryModel(params.schemaId);
return await Model.count().exec();
}
};
================================================
FILE: lib/server/graphql/queries/schema-entry/schema-list.js
================================================
import getProjection from 'helpers/get-projection';
import {
GraphQLList,
GraphQLID
} from 'graphql';
import authorize from '../../authorize';
import schemaEntryModel from '../../../models/schema-entry';
import schemaEntryType from '../../types/schema-entry';
import {paginationQueryArgs, paginateQuery, searchQuery} from '../../query-pagination';
export default {
type: new GraphQLList(schemaEntryType),
args: {
schemaId: {
name: 'schemaId',
type: GraphQLID
},
...paginationQueryArgs
},
async resolve (root, params, options) {
authorize(root);
const Model = await schemaEntryModel(params.schemaId);
const projection = getProjection(options.fieldASTs[0]);
const query = Model.find(searchQuery({}, params));
paginateQuery(query, params);
return await query.select(projection).exec();
}
};
================================================
FILE: lib/server/graphql/queries/schemas/index.js
================================================
import schemas from './schemas';
import schemasCount from './schemas-count';
import schema, {validateSchemaSlug} from './schema';
export default {
schemas,
schemasCount,
schema,
validateSchemaSlug
};
================================================
FILE: lib/server/graphql/queries/schemas/schema.js
================================================
import getProjection from 'helpers/get-projection';
import {
GraphQLString,
GraphQLBoolean,
GraphQLNonNull,
GraphQLID
} from 'graphql';
import authorize from '../../authorize';
import schemaType from '../../types/schema';
import SchemaModel from '../../../models/schema';
export default {
type: schemaType,
args: {
_id: {
name: '_id',
type: GraphQLID
},
slug: {
name: 'slug',
type: GraphQLString
}
},
async resolve (root, params, options) {
const projection = getProjection(options.fieldASTs[0]);
return await SchemaModel.findOne(params).select(projection).exec();
}
};
export const validateSchemaSlug = {
type: GraphQLBoolean,
args: {
slug: {
name: 'slug',
type: new GraphQLNonNull(GraphQLString)
},
schemaId: {
name: 'schemaId',
type: GraphQLID
}
},
async resolve (root, {slug, schemaId}) {
authorize(root);
return await SchemaModel.count({
slug,
_id: {
$ne: schemaId
}
}) === 0;
}
};
================================================
FILE: lib/server/graphql/queries/schemas/schemas-count.js
================================================
import {GraphQLInt} from 'graphql';
import authorize from '../../authorize';
import SchemaModel from '../../../models/schema';
export default {
type: GraphQLInt,
args: {},
async resolve (root) {
authorize(root);
return await SchemaModel.count();
}
};
================================================
FILE: lib/server/graphql/queries/schemas/schemas.js
================================================
import getProjection from 'helpers/get-projection';
import {
GraphQLList
} from 'graphql';
import authorize from '../../authorize';
import schemaType from '../../types/schema';
import SchemaModel from '../../../models/schema';
import {paginationQueryArgs, paginateQuery, searchQuery} from '../../query-pagination';
export default {
type: new GraphQLList(schemaType),
args: {
...paginationQueryArgs
},
resolve (root, params, options) {
authorize(root);
const projection = getProjection(options.fieldASTs[0]);
const query = SchemaModel.find(searchQuery({}, params));
paginateQuery(query, params);
return query.select(projection).exec();
}
};
================================================
FILE: lib/server/graphql/queries/settings/index.js
================================================
import settings from './settings';
export default {
settings
};
================================================
FILE: lib/server/graphql/queries/settings/settings.js
================================================
import getProjection from 'helpers/get-projection';
import {
GraphQLList,
GraphQLString
} from 'graphql';
import authorize from '../../authorize';
import settingType from '../../types/setting';
import SettingModel from '../../../models/setting';
export default {
type: new GraphQLList(settingType),
args: {
ids: {
name: 'ids',
type: new GraphQLList(GraphQLString)
}
},
resolve: (root, params, options) => {
authorize(root);
const projection = getProjection(options.fieldASTs[0]);
return SettingModel
.find({
_id: {$in: params.ids}
})
.select(projection)
.exec();
}
};
================================================
FILE: lib/server/graphql/queries/style/index.js
================================================
import styles from './styles';
export default {
styles
};
================================================
FILE: lib/server/graphql/queries/style/styles.js
================================================
import getProjection from 'helpers/get-projection';
import {
GraphQLList
} from 'graphql';
import styleType from '../../types/style';
import StyleModel from '../../../models/style';
export default {
type: new GraphQLList(styleType),
args: {},
resolve (root, params, options) {
const projection = getProjection(options.fieldASTs[0]);
return StyleModel.find().select(projection).exec();
}
};
================================================
FILE: lib/server/graphql/queries/symbol/index.js
================================================
import symbol from './symbol';
import symbols from './symbols';
export default {
symbol,
symbols
};
================================================
FILE: lib/server/graphql/queries/symbol/symbol.js
================================================
import getProjection from 'helpers/get-projection';
import {GraphQLID} from 'graphql';
import {Types} from 'mongoose';
import symbolType from '../../types/symbol';
import SymbolModel from '../../../models/symbol';
export default {
type: symbolType,
args: {
id: {
name: 'id',
type: GraphQLID
}
},
resolve (root, params, options) {
const projection = getProjection(options.fieldASTs[0]);
const _id = new Types.ObjectId(params.id);
return SymbolModel.findById(_id).select(projection).exec();
}
};
================================================
FILE: lib/server/graphql/queries/symbol/symbols.js
================================================
import getProjection from 'helpers/get-projection';
import {GraphQLList} from 'graphql';
import symbolType from '../../types/symbol';
import SymbolModel from '../../../models/symbol';
export default {
type: new GraphQLList(symbolType),
args: {},
resolve (root, params, options) {
const projection = getProjection(options.fieldASTs[0]);
return SymbolModel.find().select(projection).exec();
}
};
================================================
FILE: lib/server/graphql/queries/tab/index.js
================================================
import tabs from './tabs';
export default {
tabs
};
================================================
FILE: lib/server/graphql/queries/tab/tabs.js
================================================
import getProjection from 'helpers/get-projection';
import {GraphQLList} from 'graphql';
import authorize from '../../authorize';
import tabType from '../../types/tab';
import TabModel from '../../../models/tab';
export default {
type: new GraphQLList(tabType),
args: {},
resolve (root, params, options) {
authorize(root);
const _userId = root.user._id;
const projection = getProjection(options.fieldASTs[0]);
return TabModel
.find({_userId})
.select(projection)
.exec();
}
};
================================================
FILE: lib/server/graphql/queries/user/index.js
================================================
import session from './session';
import user from './user';
import users from './users';
import usersCount from './users-count';
export default {
user,
users,
usersCount,
session
};
================================================
FILE: lib/server/graphql/queries/user/session.js
================================================
import {GraphQLBoolean} from 'graphql';
export default {
type: GraphQLBoolean,
resolve (root) {
return root.isAuthenticated;
}
};
================================================
FILE: lib/server/graphql/queries/user/user.js
================================================
import getProjection from 'helpers/get-projection';
import {GraphQLID} from 'graphql';
import authorize from '../../authorize';
import userType from '../../types/user';
import UserModel from '../../../models/user';
export default {
type: userType,
args: {
id: {
name: 'username',
type: GraphQLID
}
},
resolve (root, params, options) {
authorize(root);
const projection = getProjection(options.fieldASTs[0]);
let id = params.id;
if (!id) {
id = root.user._id;
}
return UserModel
.findById(id)
.select(projection)
.exec();
}
};
================================================
FILE: lib/server/graphql/queries/user/users-count.js
================================================
import {GraphQLInt} from 'graphql';
import authorize from '../../authorize';
import UserModel from '../../../models/user';
export default {
type: GraphQLInt,
args: {},
async resolve (root) {
authorize(root);
return await UserModel.count();
}
};
================================================
FILE: lib/server/graphql/queries/user/users.js
================================================
import getProjection from 'helpers/get-projection';
import {GraphQLList} from 'graphql';
import authorize from '../../authorize';
import userType from '../../types/user';
import UserModel from '../../../models/user';
import {paginationQueryArgs, paginateQuery, searchQuery} from '../../query-pagination';
export default {
type: new GraphQLList(userType),
args: {
...paginationQueryArgs
},
resolve (root, params, options) {
authorize(root);
const projection = getProjection(options.fieldASTs[0]);
const query = UserModel.find(searchQuery({}, params));
paginateQuery(query, params);
return query.select(projection).exec();
}
};
================================================
FILE: lib/server/graphql/query-pagination.js
================================================
import forEach from 'lodash.foreach';
import {
GraphQLString,
GraphQLInt,
GraphQLList
} from 'graphql';
import filterType from './types/filter';
export const paginationQueryArgs = {
sort: {
name: 'sort',
type: GraphQLString
},
order: {
name: 'order',
type: GraphQLString
},
limit: {
name: 'limit',
type: GraphQLInt
},
filters: {
name: 'filters',
type: new GraphQLList(filterType)
},
page: {
name: 'page',
type: GraphQLInt
},
search: {
name: 'search',
type: GraphQLString
},
s: {
name: 's',
type: GraphQLString
}
};
function parseFilterOperation (op) {
const result = {};
forEach(op, (value, key) => {
result[`$${key}`] = value;
});
return result;
}
export function searchQuery (find, params) {
const and = [];
// Search
if (params.search && params.s) {
and.push({
[params.search]: new RegExp(`.*${params.s}`, 'i')
});
}
// Filters
if (params.filters && params.filters.constructor === Array) {
forEach(params.filters, (filter) => {
and.push({
[filter.property]: parseFilterOperation(filter.op)
});
});
}
// apply and operator with all the filters
if (and.length > 0) {
Object.assign(find, {
$and: and
});
}
return find;
}
export function paginateQuery (query, params) {
if (params.sort) {
query.sort({
[params.sort]: params.order || 'asc'
});
}
if (params.page && params.limit) {
query.skip((params.page - 1) * params.limit);
}
if (params.limit) {
query.limit(params.limit);
}
}
================================================
FILE: lib/server/graphql/types/color-input.js
================================================
import {
GraphQLInputObjectType,
GraphQLString,
GraphQLID
} from 'graphql';
const colorInputType = new GraphQLInputObjectType({
name: 'ColorInput',
fields: {
_id: {type: GraphQLID},
label: {type: GraphQLString},
value: {type: GraphQLString}
}
});
export default colorInputType;
================================================
FILE: lib/server/graphql/types/color.js
================================================
import {
GraphQLObjectType,
GraphQLNonNull,
GraphQLString,
GraphQLID
} from 'graphql';
const colorType = new GraphQLObjectType({
name: 'Color',
fields: {
_id: {type: new GraphQLNonNull(GraphQLID)},
label: {type: GraphQLString},
value: {type: GraphQLString}
}
});
export default colorType;
================================================
FILE: lib/server/graphql/types/count.js
================================================
import {
GraphQLObjectType,
GraphQLInt
} from 'graphql';
const countType = new GraphQLObjectType({
name: 'Count',
fields: {
count: {type: GraphQLInt}
}
});
export default countType;
================================================
FILE: lib/server/graphql/types/custom-font.js
================================================
import {
GraphQLObjectType,
GraphQLString,
GraphQLNonNull
} from 'graphql';
const customFontType = new GraphQLObjectType({
name: 'CustomFont',
fields: {
family: {
type: new GraphQLNonNull(GraphQLString)
},
id: {
type: new GraphQLNonNull(GraphQLString)
},
files: {
type: new GraphQLObjectType({
name: 'CustomFontFiles',
fields: {
eot: {type: new GraphQLNonNull(GraphQLString)},
woff2: {type: GraphQLString},
woff: {type: new GraphQLNonNull(GraphQLString)},
ttf: {type: new GraphQLNonNull(GraphQLString)}
}
})
}
}
});
export default customFontType;
================================================
FILE: lib/server/graphql/types/draft-id-input.js
================================================
import {
GraphQLInputObjectType,
GraphQLString
} from 'graphql';
export default new GraphQLInputObjectType({
name: 'DraftIdInput',
fields: {
_id: {
type: GraphQLString
},
_userId: {
type: GraphQLString
}
}
});
================================================
FILE: lib/server/graphql/types/draft-id.js
================================================
import {
GraphQLObjectType,
GraphQLNonNull,
GraphQLString
} from 'graphql';
export default new GraphQLObjectType({
name: 'DraftId',
fields: {
_id: {
type: new GraphQLNonNull(GraphQLString)
},
_userId: {
type: new GraphQLNonNull(GraphQLString)
}
}
});
================================================
FILE: lib/server/graphql/types/draft-input.js
================================================
import {
GraphQLInputObjectType,
GraphQLString,
GraphQLInt
} from 'graphql';
import draftIdInputType from './draft-id-input';
const draftInputType = new GraphQLInputObjectType({
name: 'DraftInput',
fields: {
_id: {
type: draftIdInputType
},
__v: {
type: GraphQLInt
},
data: {
type: GraphQLString
},
actions: {
type: GraphQLString
},
schemaLinks: {
type: GraphQLString
}
}
});
export default draftInputType;
================================================
FILE: lib/server/graphql/types/draft.js
================================================
import {
GraphQLObjectType,
GraphQLNonNull,
GraphQLString,
GraphQLInt
} from 'graphql';
import draftIdType from './draft-id';
export default new GraphQLObjectType({
name: 'Draft',
fields: {
_id: {
type: new GraphQLNonNull(draftIdType)
},
__v: {
type: GraphQLInt
},
data: {
type: GraphQLString,
resolve: (draft) => JSON.stringify(draft.data)
},
actions: {
type: GraphQLString,
resolve: (draft) => JSON.stringify(draft.actions)
},
schemaLinks: {
type: GraphQLString,
resolve: (draft) => JSON.stringify(draft.schemaLinks)
}
}
});
================================================
FILE: lib/server/graphql/types/filter.js
================================================
import {
GraphQLInputObjectType,
GraphQLNonNull,
GraphQLString,
GraphQLList
} from 'graphql';
const filterType = new GraphQLInputObjectType({
name: 'Filter',
fields: {
property: {
type: new GraphQLNonNull(GraphQLString)
},
op: {
type: new GraphQLInputObjectType({
name: 'FilterOp',
fields: {
eq: {type: GraphQLString},
in: {type: new GraphQLList(GraphQLString)}
}
})
}
}
});
export default filterType;
================================================
FILE: lib/server/graphql/types/generators/schema-entry-input.js
================================================
import forEach from 'lodash.foreach';
import {
GraphQLInputObjectType,
GraphQLNonNull,
GraphQLString,
GraphQLInt,
GraphQLID
} from 'graphql';
import {TypesNativeGraphQL} from 'helpers/data-types/native';
export default (schema) => {
const propertiesFields = {};
forEach(schema.properties, (property) => {
if (TypesNativeGraphQL[property.type]) {
const native = TypesNativeGraphQL[property.type];
propertiesFields[property.id] = Object.assign({}, native);
}
});
return new GraphQLInputObjectType({
name: `rlx_${schema.slug}_input`,
fields: {
_id: {type: new GraphQLNonNull(GraphQLID)},
title: {type: GraphQLString},
slug: {type: GraphQLString},
__v: {type: GraphQLInt},
state: {type: GraphQLString},
date: {
type: GraphQLInt,
resolve: () => Date.now()
},
updatedDate: {
type: GraphQLInt,
resolve: () => Date.now()
},
updatedBy: {type: GraphQLID},
createdBy: {type: GraphQLID},
data: {type: GraphQLString},
...propertiesFields
}
});
};
================================================
FILE: lib/server/graphql/types/generators/schema-entry.js
================================================
import forEach from 'lodash.foreach';
import {
GraphQLObjectType,
GraphQLNonNull,
GraphQLString,
GraphQLInt,
GraphQLID
} from 'graphql';
import {TypesNativeGraphQL} from 'helpers/data-types/native';
import userType from '../user';
import UserModel from '../../../models/user';
export default (schema) => {
const propertiesFields = {};
forEach(schema.properties, (property) => {
if (TypesNativeGraphQL[property.type]) {
const native = TypesNativeGraphQL[property.type];
propertiesFields[property.id] = Object.assign({}, native);
}
});
return new GraphQLObjectType({
name: `rlx_${schema.slug}`,
fields: {
_id: {type: new GraphQLNonNull(GraphQLID)},
title: {type: GraphQLString},
slug: {type: new GraphQLNonNull(GraphQLString)},
__v: {type: GraphQLInt},
state: {type: GraphQLString},
date: {
type: GraphQLInt,
resolve ({date}) {
return date && date.getTime();
}
},
updatedDate: {
type: GraphQLInt,
resolve ({updatedDate}) {
return updatedDate && updatedDate.getTime();
}
},
updatedBy: {
type: userType,
async resolve (schemaEntry) {
return await UserModel.findById(schemaEntry.updatedBy).exec();
}
},
createdBy: {
type: userType,
async resolve (schemaEntry) {
return await UserModel.findById(schemaEntry.createdBy).exec();
}
},
data: {
type: GraphQLString,
resolve (schemaEntry) {
return JSON.stringify(schemaEntry.data);
}
},
...propertiesFields
}
});
};
================================================
FILE: lib/server/graphql/types/media-input.js
================================================
import {
GraphQLInputObjectType,
GraphQLString,
GraphQLNonNull
} from 'graphql';
const mediaInputType = new GraphQLInputObjectType({
name: 'MediaInput',
fields: {
file: {
type: new GraphQLInputObjectType({
name: 'MediaInputFile',
fields: {
file: {type: new GraphQLNonNull(GraphQLString)},
filename: {type: new GraphQLNonNull(GraphQLString)}
}
})
}
}
});
export default mediaInputType;
================================================
FILE: lib/server/graphql/types/media.js
================================================
import {
GraphQLObjectType,
GraphQLNonNull,
GraphQLString,
GraphQLInt,
GraphQLFloat,
GraphQLID
} from 'graphql';
const mediaType = new GraphQLObjectType({
name: 'Media',
fields: {
_id: {type: new GraphQLNonNull(GraphQLID)},
name: {type: new GraphQLNonNull(GraphQLString)},
fileName: {type: new GraphQLNonNull(GraphQLString)},
type: {type: new GraphQLNonNull(GraphQLString)},
size: {type: new GraphQLNonNull(GraphQLString)},
filesize: {type: new GraphQLNonNull(GraphQLString)},
dimension: {
type: new GraphQLObjectType({
name: 'MediaDimension',
fields: {
width: {type: GraphQLInt},
height: {type: GraphQLInt}
}
})
},
url: {type: new GraphQLNonNull(GraphQLString)},
absoluteUrl: {type: new GraphQLNonNull(GraphQLString)},
date: {
type: GraphQLFloat,
resolve: ({date}) => (date && date.getTime())
},
thumbnail: {type: GraphQLString},
variations: {
type: GraphQLString,
resolve (media) {
return JSON.stringify(media.variations);
}
}
}
});
export default mediaType;
================================================
FILE: lib/server/graphql/types/menu-data.js
================================================
import getProjection from 'helpers/get-projection';
import {
GraphQLObjectType,
GraphQLString,
GraphQLID,
GraphQLList
} from 'graphql';
import pageType from './page';
import PageModel from '../../models/page';
const menuDataType = new GraphQLObjectType({
name: 'MenuData',
fields: () => ({
id: {type: GraphQLID},
type: {type: GraphQLString},
page: {
type: pageType,
resolve (menuData, params, options) {
const projection = getProjection(options.fieldASTs[0]);
return PageModel
.findById(menuData.page)
.select(projection)
.exec();
}
},
link: {
type: new GraphQLObjectType({
name: 'MenuDataLink',
fields: {
url: {type: GraphQLString},
label: {type: GraphQLString}
}
})
},
children: {
type: new GraphQLList(menuDataType)
}
})
});
export default menuDataType;
================================================
FILE: lib/server/graphql/types/menu-input.js
================================================
import {
GraphQLInputObjectType,
GraphQLString,
GraphQLFloat,
GraphQLID
} from 'graphql';
const menuInputType = new GraphQLInputObjectType({
name: 'MenuInput',
fields: {
_id: {type: GraphQLID},
title: {type: GraphQLString},
date: {
type: GraphQLFloat,
resolve: () => Date.now()
},
updatedDate: {
type: GraphQLFloat,
resolve: () => Date.now()
},
updatedBy: {type: GraphQLString},
createdBy: {type: GraphQLString},
data: {type: GraphQLString}
}
});
export default menuInputType;
================================================
FILE: lib/server/graphql/types/menu.js
================================================
import {
GraphQLObjectType,
GraphQLNonNull,
GraphQLString,
GraphQLFloat,
GraphQLID
} from 'graphql';
import userType from './user';
import UserModel from '../../models/user';
const menuType = new GraphQLObjectType({
name: 'Menu',
fields: {
_id: {type: new GraphQLNonNull(GraphQLID)},
title: {type: GraphQLString},
date: {
type: GraphQLFloat,
resolve ({date}) {
return date && date.getTime();
}
},
updatedDate: {
type: GraphQLFloat,
resolve ({updatedDate}) {
return updatedDate && updatedDate.getTime();
}
},
updatedBy: {
type: userType,
resolve (menu) {
return UserModel.findById(menu.updatedBy).exec();
}
},
createdBy: {
type: userType,
resolve (menu) {
return UserModel.findById(menu.createdBy).exec();
}
},
data: {
type: GraphQLString,
resolve ({data}) {
// TODO fetch needed data from nodes
return JSON.stringify(data);
}
}
}
});
export default menuType;
================================================
FILE: lib/server/graphql/types/page-input.js
================================================
import {
GraphQLInputObjectType,
GraphQLString,
GraphQLInt,
GraphQLFloat,
GraphQLID
} from 'graphql';
const pageInputType = new GraphQLInputObjectType({
name: 'PageInput',
fields: {
_id: {type: GraphQLID},
slug: {type: GraphQLString},
__v: {type: GraphQLInt},
state: {type: GraphQLString},
date: {
type: GraphQLFloat,
resolve: () => Date.now()
},
updatedDate: {
type: GraphQLFloat,
resolve: () => Date.now()
},
title: {type: GraphQLString},
data: {type: GraphQLString},
updatedBy: {type: GraphQLID},
createdBy: {type: GraphQLID}
}
});
export default pageInputType;
================================================
FILE: lib/server/graphql/types/page.js
================================================
import {
GraphQLObjectType,
GraphQLNonNull,
GraphQLString,
GraphQLInt,
GraphQLFloat,
GraphQLID
} from 'graphql';
import userType from './user';
import UserModel from '../../models/user';
const pageType = new GraphQLObjectType({
name: 'Page',
fields: {
_id: {type: new GraphQLNonNull(GraphQLID)},
slug: {type: new GraphQLNonNull(GraphQLString)},
__v: {type: GraphQLInt},
state: {type: GraphQLString},
date: {
type: GraphQLFloat,
resolve ({date}) {
return date && date.getTime();
}
},
updatedDate: {
type: GraphQLFloat,
resolve ({updatedDate}) {
return updatedDate && updatedDate.getTime();
}
},
title: {type: GraphQLString},
data: {
type: GraphQLString,
resolve (page) {
return JSON.stringify(page.data);
}
},
updatedBy: {
type: userType,
async resolve (page) {
return await UserModel.findById(page.updatedBy).exec();
}
},
createdBy: {
type: userType,
async resolve (page) {
return await UserModel.findById(page.createdBy).exec();
}
}
}
});
export default pageType;
================================================
FILE: lib/server/graphql/types/revision.js
================================================
import {
GraphQLObjectType,
GraphQLNonNull,
GraphQLString,
GraphQLInt,
GraphQLFloat,
GraphQLID
} from 'graphql';
import userType from './user';
import UserModel from '../../models/user';
const revisionType = new GraphQLObjectType({
name: 'Revision',
fields: {
_id: {
type: new GraphQLObjectType({
name: 'RevisionId',
fields: {
_id: {type: new GraphQLNonNull(GraphQLID)},
__v: {type: new GraphQLNonNull(GraphQLInt)}
}
})
},
date: {
type: GraphQLFloat,
resolve ({date}) {
return date && date.getTime();
}
},
doc: {
type: new GraphQLNonNull(GraphQLString),
resolve (revision) {
return JSON.stringify(revision.doc);
}
},
user: {
type: userType,
async resolve (revision) {
return await UserModel.findById(revision.user).exec();
}
}
}
});
export default revisionType;
================================================
FILE: lib/server/graphql/types/schema-entry-input.js
================================================
import {
GraphQLInputObjectType,
GraphQLString,
GraphQLInt,
GraphQLFloat,
GraphQLID
} from 'graphql';
export default new GraphQLInputObjectType({
name: 'SchemaEntryInput',
fields: {
_id: {type: GraphQLID},
title: {type: GraphQLString},
slug: {type: GraphQLString},
__v: {type: GraphQLInt},
state: {type: GraphQLString},
date: {
type: GraphQLFloat,
resolve: () => Date.now()
},
updatedDate: {
type: GraphQLFloat,
resolve: () => Date.now()
},
updatedBy: {type: GraphQLID},
createdBy: {type: GraphQLID},
data: {type: GraphQLString},
properties: {type: GraphQLString}
}
});
================================================
FILE: lib/server/graphql/types/schema-entry.js
================================================
import {
GraphQLObjectType,
GraphQLNonNull,
GraphQLString,
GraphQLInt,
GraphQLFloat,
GraphQLID
} from 'graphql';
import userType from './user';
import UserModel from '../../models/user';
export default new GraphQLObjectType({
name: 'SchemaEntry',
fields: {
_id: {type: new GraphQLNonNull(GraphQLID)},
title: {type: GraphQLString},
slug: {type: new GraphQLNonNull(GraphQLString)},
__v: {type: GraphQLInt},
state: {type: GraphQLString},
date: {
type: GraphQLFloat,
resolve ({date}) {
return date && date.getTime();
}
},
publishedDate: {
type: GraphQLFloat,
resolve ({publishedDate}) {
return publishedDate && publishedDate.getTime();
}
},
updatedDate: {
type: GraphQLFloat,
resolve ({updatedDate}) {
return updatedDate && updatedDate.getTime();
}
},
updatedBy: {
type: userType,
async resolve (schemaEntry) {
return await UserModel.findById(schemaEntry.updatedBy).exec();
}
},
createdBy: {
type: userType,
async resolve (schemaEntry) {
return await UserModel.findById(schemaEntry.createdBy).exec();
}
},
data: {
type: GraphQLString,
resolve (schemaEntry) {
return JSON.stringify(schemaEntry.data);
}
},
properties: {
type: GraphQLString,
resolve (schemaEntry) {
return JSON.stringify(schemaEntry.properties);
}
}
}
});
================================================
FILE: lib/server/graphql/types/schema-input.js
================================================
import {
GraphQLInputObjectType,
GraphQLString,
GraphQLInt,
GraphQLFloat,
GraphQLID
} from 'graphql';
const schemaInputType = new GraphQLInputObjectType({
name: 'SchemaInput',
fields: {
_id: {type: GraphQLString},
__v: {type: GraphQLInt},
title: {type: GraphQLString},
slug: {type: GraphQLString},
type: {type: GraphQLString},
date: {
type: GraphQLFloat,
resolve: () => Date.now()
},
updatedDate: {
type: GraphQLFloat,
resolve: () => Date.now()
},
data: {type: GraphQLString},
schemaLinks: {type: GraphQLString},
updatedBy: {type: GraphQLID},
createdBy: {type: GraphQLID},
properties: {type: GraphQLString}
}
});
export default schemaInputType;
================================================
FILE: lib/server/graphql/types/schema.js
================================================
import {
GraphQLObjectType,
GraphQLNonNull,
GraphQLString,
GraphQLInt,
GraphQLFloat,
GraphQLID
} from 'graphql';
import userType from './user';
import UserModel from '../../models/user';
const schemaType = new GraphQLObjectType({
name: 'Schema',
fields: {
_id: {
type: new GraphQLNonNull(GraphQLID)
},
title: {
type: GraphQLString
},
slug: {
type: new GraphQLNonNull(GraphQLString)
},
type: {
type: GraphQLString
},
__v: {
type: GraphQLInt
},
date: {
type: GraphQLFloat,
resolve: ({date}) => (date && date.getTime())
},
updatedDate: {
type: GraphQLFloat,
resolve: ({updatedDate}) => (updatedDate && updatedDate.getTime())
},
data: {
type: GraphQLString,
resolve: (schema) => JSON.stringify(schema.data)
},
schemaLinks: {
type: GraphQLString,
resolve: (schema) => JSON.stringify(schema.schemaLinks)
},
updatedBy: {
type: userType,
resolve: (schema) => UserModel.findById(schema.updatedBy).exec()
},
createdBy: {
type: userType,
resolve: (schema) => UserModel.findById(schema.createdBy).exec()
},
properties: {
type: GraphQLString,
resolve: (schema) => JSON.stringify(schema.properties)
}
}
});
export default schemaType;
================================================
FILE: lib/server/graphql/types/setting-input.js
================================================
import {
GraphQLInputObjectType,
GraphQLString
} from 'graphql';
const settingInputType = new GraphQLInputObjectType({
name: 'SettingInput',
fields: {
_id: {type: GraphQLString},
value: {type: GraphQLString}
}
});
export default settingInputType;
================================================
FILE: lib/server/graphql/types/setting.js
================================================
import {
GraphQLObjectType,
GraphQLNonNull,
GraphQLString
} from 'graphql';
const settingType = new GraphQLObjectType({
name: 'Setting',
fields: {
_id: {type: new GraphQLNonNull(GraphQLString)},
value: {type: GraphQLString}
}
});
export default settingType;
================================================
FILE: lib/server/graphql/types/style-input.js
================================================
import {
GraphQLInputObjectType,
GraphQLString,
GraphQLID
} from 'graphql';
const styleInputType = new GraphQLInputObjectType({
name: 'StyleInput',
fields: {
_id: {type: GraphQLID},
title: {type: GraphQLString},
type: {type: GraphQLString},
options: {type: GraphQLString},
displayOptions: {type: GraphQLString}
}
});
export default styleInputType;
================================================
FILE: lib/server/graphql/types/style.js
================================================
import {
GraphQLObjectType,
GraphQLNonNull,
GraphQLString,
GraphQLID
} from 'graphql';
const styleType = new GraphQLObjectType({
name: 'Style',
fields: {
_id: {type: new GraphQLNonNull(GraphQLID)},
type: {type: new GraphQLNonNull(GraphQLString)},
title: {type: GraphQLString},
options: {
type: GraphQLString,
resolve (style) {
return JSON.stringify(style.options);
}
},
displayOptions: {
type: GraphQLString,
resolve (style) {
return JSON.stringify(style.displayOptions);
}
}
}
});
export default styleType;
================================================
FILE: lib/server/graphql/types/symbol-input.js
================================================
import {
GraphQLInputObjectType,
GraphQLString,
GraphQLID
} from 'graphql';
const symbolInputType = new GraphQLInputObjectType({
name: 'SymbolInput',
fields: {
_id: {type: GraphQLID},
title: {type: GraphQLString},
data: {type: GraphQLString}
}
});
export default symbolInputType;
================================================
FILE: lib/server/graphql/types/symbol.js
================================================
import {
GraphQLObjectType,
GraphQLNonNull,
GraphQLString,
GraphQLID
} from 'graphql';
const symbolType = new GraphQLObjectType({
name: 'Symbol',
fields: {
_id: {type: new GraphQLNonNull(GraphQLID)},
title: {type: GraphQLString},
data: {
type: GraphQLString,
resolve (symbol) {
return JSON.stringify(symbol.data);
}
}
}
});
export default symbolType;
================================================
FILE: lib/server/graphql/types/tab-item.js
================================================
import {
GraphQLObjectType,
GraphQLNonNull,
GraphQLString
} from 'graphql';
export default new GraphQLObjectType({
name: 'TabItem',
fields: {
_id: {
type: new GraphQLNonNull(GraphQLString)
},
title: {
type: GraphQLString
}
}
});
================================================
FILE: lib/server/graphql/types/tab.js
================================================
import {
GraphQLObjectType,
GraphQLNonNull,
GraphQLID,
GraphQLString
} from 'graphql';
import tabItemType from './tab-item';
import PageModel from '../../models/page';
export default new GraphQLObjectType({
name: 'Tab',
fields: {
_id: {
type: new GraphQLNonNull(GraphQLID)
},
_userId: {
type: GraphQLID
},
type: {
type: new GraphQLNonNull(GraphQLString)
},
item: {
type: tabItemType,
async resolve ({type, item}) {
let result = null;
if (type === 'page') {
result = await PageModel.findById(item).exec();
}
return result;
}
}
}
});
================================================
FILE: lib/server/graphql/types/uploaded-input.js
================================================
import {
GraphQLInputObjectType,
GraphQLString,
GraphQLInt
} from 'graphql';
const uploadedInputType = new GraphQLInputObjectType({
name: 'UploadedFileInput',
fields: {
originalname: {type: GraphQLString},
mimetype: {type: GraphQLString},
destination: {type: GraphQLString},
filename: {type: GraphQLString},
path: {type: GraphQLString},
size: {type: GraphQLInt}
}
});
export default uploadedInputType;
================================================
FILE: lib/server/graphql/types/uploaded.js
================================================
import {
GraphQLObjectType,
GraphQLString,
GraphQLInt
} from 'graphql';
const uploadedType = new GraphQLObjectType({
name: 'UploadedFile',
fields: {
originalname: {type: GraphQLString},
mimetype: {type: GraphQLString},
destination: {type: GraphQLString},
filename: {type: GraphQLString},
path: {type: GraphQLString},
size: {type: GraphQLInt}
}
});
export default uploadedType;
================================================
FILE: lib/server/graphql/types/user-input.js
================================================
import {
GraphQLInputObjectType,
GraphQLString,
GraphQLNonNull
} from 'graphql';
const userInputType = new GraphQLInputObjectType({
name: 'UserInput',
fields: {
_id: {type: GraphQLString},
username: {type: new GraphQLNonNull(GraphQLString)},
name: {type: GraphQLString},
password: {type: GraphQLString},
email: {type: GraphQLString},
date: {type: GraphQLString}
}
});
export default userInputType;
================================================
FILE: lib/server/graphql/types/user.js
================================================
import {
GraphQLObjectType,
GraphQLNonNull,
GraphQLString
} from 'graphql';
const userType = new GraphQLObjectType({
name: 'User',
fields: {
_id: {type: new GraphQLNonNull(GraphQLString)},
username: {type: new GraphQLNonNull(GraphQLString)},
name: {type: GraphQLString},
email: {type: GraphQLString},
date: {type: GraphQLString}
}
});
export default userType;
================================================
FILE: lib/server/index.js
================================================
import bodyParser from 'body-parser';
import connectMongo from 'connect-mongo';
import express from 'express';
import graphqlHTTP from 'express-graphql';
import mongoose from 'mongoose';
import morgan from 'morgan';
import multer from 'multer';
import parseSettings from 'helpers/parse-settings';
import passport from 'passport';
import path from 'path';
import safeHtmlString from 'helpers/safe-html-string';
import session from 'express-session';
import config from '../../config';
import middleware from './middleware';
import routers from './routers';
import schema from './schema';
import SettingModel from './models/setting';
const app = express();
app.use(morgan('short'));
app.use(bodyParser.urlencoded({extended: false}));
app.use(bodyParser.json({limit: 100000000}));
// View engine
app.set('views', path.join(__dirname, 'components'));
// session
const MongoStore = connectMongo(session);
app.use(session({
secret: 'Is very secret',
store: new MongoStore({mongooseConnection: mongoose.connection})
}));
// Passport
app.use(passport.initialize());
app.use(passport.session());
// Static files
app.use(express.static('./public'));
app.use(['favicon.ico', '/images*', '/media*', '/css*', '/fonts*', '/js*'], (req, res) => {
res.status(404).end();
});
// Multer
app.use('/graphql', multer({dest: './uploads/'}).single('file'));
// GraphqQL server
app.use('/graphql', graphqlHTTP(req => ({
schema: schema.getSchema(),
rootValue: {
isAuthenticated: req.isAuthenticated(),
user: req.user,
file: req.file
},
graphiql: true
})));
app.use(async (req, res, next) => {
const settingsArr = await SettingModel
.find({
_id: {$in: ['title', 'favicon']}
})
.exec();
const settings = parseSettings(settingsArr);
res.locals.header = [
{
tag: 'title',
content: settings.title && safeHtmlString(settings.title) || 'Relax CMS'
}
];
if (process.env.NODE_ENV !== 'production') {
res.baseScriptsURL = `http://localhost:${config.devPort}`;
res.locals.header.push({
tag: 'script',
props: {
src: `${res.baseScriptsURL}/webpack-dev-server.js`
}
});
} else {
res.baseScriptsURL = '';
}
// footer
res.locals.footer = [{
tag: 'script',
props: {
src: `${res.baseScriptsURL}/js/common.js`
}
}];
next();
});
app.use(middleware.fonts);
app.use(middleware.googleAnalytics);
app.use(routers.authRouter);
app.use(routers.adminRouter);
app.use(routers.publicRouter);
app.use((req, res) => {
res.status(404).end();
});
app.use((error, req, res) => {
const statusCode = error.statusCode || 500;
const err = {
error: statusCode,
message: error.message
};
if (!res.headersSent) {
res.status(statusCode).send(err);
}
});
export default app;
================================================
FILE: lib/server/logger.js
================================================
import winston from 'winston';
export default new winston.Logger({
transports: [
new winston.transports.Console({
level: 'debug',
handleExceptions: true,
json: false,
colorize: true
})
],
exitOnError: false
});
================================================
FILE: lib/server/middleware/fonts.js
================================================
import forEach from 'lodash.foreach';
import SettingModel from '../models/setting';
export default async (req, res, next) => {
res.locals.header.push({
tag: 'script',
props: {
src: '//ajax.googleapis.com/ajax/libs/webfont/1.5.10/webfont.js'
}
});
try {
const fontsSetting = await SettingModel.findOne({_id: 'fonts'});
if (fontsSetting && fontsSetting.value) {
const fonts = JSON.parse(fontsSetting.value);
if (fonts.customFonts) {
let css = '';
forEach(fonts.customFonts, (customFont) => {
const family = customFont.family;
const map = customFont.files;
const location = `/fonts/${customFont.id}/`;
css += `@font-face {
font-family: "${family}";
src: url("${location}${map.eot}");
src:
`;
if (map.woff2) {
css += `url("${location}${map.woff2}"), `;
}
css += `
url("${location}${map.woff}"),
url("${location}${map.ttf}");
}
`;
});
if (css !== '') {
res.locals.header.push({
tag: 'style',
props: {
type: 'text/css'
},
content: css
});
}
}
if (fonts.webfontloader) {
res.locals.header.push({
tag: 'script',
content: `WebFont.load(${JSON.stringify(fonts.webfontloader)});`
});
}
}
next();
} catch (err) {
next();
}
};
================================================
FILE: lib/server/middleware/google-analytics.js
================================================
import utils from 'helpers/utils';
import SettingModel from '../models/setting';
export default async (req, res, next) => {
res.locals.header.push({
tag: 'script',
props: {
src: '//ajax.googleapis.com/ajax/libs/webfont/1.5.10/webfont.js'
}
});
const googleAnalyticsSetting = await SettingModel
.findById('googleAnalytics')
.exec();
if (googleAnalyticsSetting &&
googleAnalyticsSetting.value &&
utils.validateGATrackingId(googleAnalyticsSetting.value)) {
res.locals.header.push({
tag: 'script',
content: `
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','//www.google-analytics.com/analytics.js','ga');
ga('create', '${googleAnalyticsSetting.value}', 'auto');
`
});
next();
} else {
next();
}
};
================================================
FILE: lib/server/middleware/index.js
================================================
import fonts from './fonts';
import googleAnalytics from './google-analytics';
export default {
fonts,
googleAnalytics
};
================================================
FILE: lib/server/migrate.js
================================================
import q from 'q';
import semver from 'semver';
import {readdirSync} from 'fs';
import {basename, extname, join} from 'path';
import logger from './logger';
import MigrationModel from './models/migration';
const migrationsPath = './migrations';
function saveMigration (path) {
return new MigrationModel({_id: path}).save();
}
function runMigration (path) {
const migration = require(join(migrationsPath, path));
return migration()
.then(() => saveMigration(path))
.then(() => logger.info(`migration ${path} was applied`));
}
export default function migrate () {
return q()
.then(() => {
let promise = q();
readdirSync(migrationsPath)
.map((path) => (extname(path) === '.js' ? basename(path) : false))
.filter((path) => path && semver.valid(path.split('-')[0]))
.sort((a, b) => semver.compare(a.split('-')[0], b.split('-')[0]))
.forEach((path) => {
promise = promise
.then(() => MigrationModel.findOne({_id: path}).exec())
.then((migration) => !migration && runMigration(path));
});
return promise;
});
}
================================================
FILE: lib/server/models/color.js
================================================
import mongoose from 'mongoose';
const colorSchema = new mongoose.Schema({
label: {
type: String,
required: true
},
value: {
type: String,
required: true
}
});
export default mongoose.model('Color', colorSchema);
================================================
FILE: lib/server/models/draft.js
================================================
import mongoose from 'mongoose';
const draftSchema = new mongoose.Schema({
_id: {
_id: {
type: mongoose.Schema.Types.ObjectId
},
_userId: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User'
}
},
data: {
type: mongoose.Schema.Types.Mixed,
default: {}
},
actions: {
type: Array,
default: []
},
schemaLinks: {
type: mongoose.Schema.Types.Mixed,
default: {}
}
});
export default mongoose.model('Draft', draftSchema);
================================================
FILE: lib/server/models/index.js
================================================
import color from './color';
import draft from './draft';
import media from './media';
import menu from './menu';
import page from './page';
import revision from './revision';
import schema from './schema';
import setting from './setting';
import style from './style';
import tab from './tab';
import user from './user';
export default [
color,
draft,
media,
menu,
page,
revision,
schema,
setting,
style,
tab,
user
];
================================================
FILE: lib/server/models/media.js
================================================
import mongoose from 'mongoose';
const mediaSchema = new mongoose.Schema({
name: {
type: String,
required: true
},
fileName: {
type: String,
required: true
},
type: {
type: String,
required: true
},
size: {
type: String,
required: true
},
filesize: {
type: Number,
required: true
},
dimension: {
width: {
type: Number
},
height: {
type: Number
}
},
url: {
type: String,
required: true,
unique: true
},
absoluteUrl: {
type: String,
required: true,
unique: true
},
date: {
type: Date,
default: Date.now
},
thumbnail: {
type: String
},
variations: {
type: Array
}
});
export default mongoose.model('Media', mediaSchema);
================================================
FILE: lib/server/models/menu.js
================================================
import mongoose from 'mongoose';
const menuSchema = new mongoose.Schema({
title: {
type: String,
required: true
},
date: {
type: Date,
default: Date.now
},
updatedDate: {
type: Date,
default: Date.now
},
createdBy: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User'
},
updatedBy: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User'
},
data: {}
}, {minimize: false});
export default mongoose.model('Menu', menuSchema);
================================================
FILE: lib/server/models/migration.js
================================================
import mongoose from 'mongoose';
const migrationSchema = new mongoose.Schema({
_id: {
type: String
},
when: {
type: Date,
default: Date.now
}
});
export default mongoose.model('Migration', migrationSchema);
================================================
FILE: lib/server/models/page.js
================================================
import mongoose from 'mongoose';
const pageSchema = new mongoose.Schema({
title: {
type: String,
required: true
},
slug: {
type: String,
required: true,
unique: true
},
state: {
type: String,
default: 'draft'
},
date: {
type: Date,
default: Date.now
},
updatedDate: {
type: Date,
default: Date.now
},
data: {
type: mongoose.Schema.Types.Mixed,
default: {}
},
createdBy: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User'
},
updatedBy: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User'
}
});
export default mongoose.model('Page', pageSchema);
================================================
FILE: lib/server/models/revision.js
================================================
import mongoose from 'mongoose';
const schema = new mongoose.Schema({
_id: {
_id: {
type: mongoose.Schema.Types.ObjectId,
required: true
},
__v: {
type: Number,
required: true
}
},
date: {
type: Date,
default: Date.now
},
user: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User'
},
doc: {
type: mongoose.Schema.Types.Mixed,
required: true
}
});
export default mongoose.model('Revision', schema);
================================================
FILE: lib/server/models/schema-entry.js
================================================
import forEach from 'lodash.foreach';
import mongoose from 'mongoose';
import {TypesNative} from 'helpers/data-types/native';
import SchemaModel from './schema';
// import tabsStore from '../stores/tabs';
// import revisionsStore from '../stores/revisions';
const models = {};
export default async (schemaId) => {
if (models[schemaId]) {
return models[schemaId];
}
const schema = await SchemaModel.findById(schemaId).exec();
const properties = {};
forEach(schema.properties, (property) => {
if (TypesNative[property.type]) {
const native = TypesNative[property.type];
properties[property.id] = {
type: native,
required: property.required
};
}
});
const mongooseSchema = {
title: {
type: String,
required: true
},
slug: {
type: String,
required: true,
unique: true
},
state: {
type: String,
default: 'draft'
},
date: {
type: Date,
default: Date.now
},
publishedDate: {
type: Date,
default: Date.now
},
updatedDate: {
type: Date,
default: Date.now
},
data: {
type: Array
},
schemaLinks: {
type: mongoose.Schema.Types.Mixed
},
overlap: {
type: Boolean,
default: false
},
createdBy: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User'
},
updatedBy: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User'
},
properties
};
const entrySchema = new mongoose.Schema(mongooseSchema, {collection: schema.slug});
// entrySchema.post('remove', (schemaEntry) => {
// tabsStore.removeMultiple({
// '_id._id': schemaEntry._id
// });
// revisionsStore.removeMultiple({
// '_id._id': schemaEntry._id
// });
// });
const model = mongoose.model(schema.slug, entrySchema);
models[schemaId] = model;
return model;
};
================================================
FILE: lib/server/models/schema.js
================================================
import mongoose from 'mongoose';
// import tabsStore from '../stores/tabs';
// import revisionsStore from '../stores/revisions';
const schemaSchema = new mongoose.Schema({
title: {
type: String,
required: true
},
slug: {
type: String,
required: true,
unique: true
},
type: {
type: String,
required: true
},
data: {
type: Array,
default: []
},
schemaLinks: {
type: mongoose.Schema.Types.Mixed,
default: {}
},
date: {
type: Date,
default: Date.now
},
updatedDate: {
type: Date,
default: Date.now
},
createdBy: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User'
},
updatedBy: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User'
},
properties: [
{
id: {
type: String,
required: true
},
title: {
type: String,
required: true
},
type: {
type: String,
required: true
},
props: {
type: mongoose.Schema.Types.Mixed,
required: false
},
default: {},
required: {
type: Boolean,
required: true,
default: false
},
dependencies: {
type: Array,
required: false
}
}
]
});
// schemaSchema.post('remove', (schema) => {
// tabsStore.removeMultiple({
// '_id._id': schema._id
// });
//
// revisionsStore.removeMultiple({
// '_id._id': schema._id
// });
// });
export default mongoose.model('Schema', schemaSchema);
================================================
FILE: lib/server/models/setting.js
================================================
import mongoose from 'mongoose';
const settingSchema = new mongoose.Schema({
_id: {
type: String
},
value: {
type: mongoose.Schema.Types.Mixed,
required: true
}
});
export default mongoose.model('Setting', settingSchema);
================================================
FILE: lib/server/models/style.js
================================================
import mongoose from 'mongoose';
const styleSchema = new mongoose.Schema({
type: {
type: String,
required: true
},
title: {
type: String,
required: true
},
options: {
type: mongoose.Schema.Types.Mixed,
required: true
},
displayOptions: {
type: mongoose.Schema.Types.Mixed
}
});
export default mongoose.model('Style', styleSchema);
================================================
FILE: lib/server/models/symbol.js
================================================
import mongoose from 'mongoose';
const symbolSchema = new mongoose.Schema({
title: {
type: String,
required: true
},
data: {
type: mongoose.Schema.Types.Mixed,
required: true
}
});
export default mongoose.model('Symbol', symbolSchema);
================================================
FILE: lib/server/models/tab.js
================================================
import mongoose from 'mongoose';
const tabSchema = new mongoose.Schema({
_userId: {
type: mongoose.Schema.Types.ObjectId
},
type: {
type: String
},
item: {
type: mongoose.Schema.Types.ObjectId
}
});
export default mongoose.model('Tab', tabSchema);
================================================
FILE: lib/server/models/user.js
================================================
import mongoose from 'mongoose';
import passport from 'passport';
import passportLocalMongoose from 'passport-local-mongoose';
import {Strategy} from 'passport-local';
const userSchema = new mongoose.Schema({
username: {
type: String,
required: true,
unique: true
},
name: {
type: String,
required: true
},
password: {
type: String
},
email: {
type: String,
unique: true,
trim: true,
required: true
},
date: {
type: Date,
default: Date.now
}
});
userSchema.plugin(passportLocalMongoose);
const UserModel = mongoose.model('User', userSchema);
passport.use(new Strategy(UserModel.authenticate()));
passport.serializeUser(UserModel.serializeUser());
passport.deserializeUser(UserModel.deserializeUser());
export default UserModel;
================================================
FILE: lib/server/routers/admin.js
================================================
import getDefaultFavicon from 'helpers/default-favicon';
import getMarkup from 'helpers/get-markup';
import routeHandler from 'helpers/route-handler';
import routes from 'routers/admin';
import {Router} from 'express';
const adminRouter = new Router();
// Restrict from here onwards
adminRouter.get('/admin*', (req, res, next) => {
if (req.isAuthenticated()) {
res.locals.footer.push({
tag: 'link',
props: {
rel: 'stylesheet',
type: 'text/css',
href: 'https://fonts.googleapis.com/css?family=Open+Sans:400,600,700'
}
});
res.locals.header.push(getDefaultFavicon(res));
if (process.env.NODE_ENV === 'production') {
res.locals.header.push({
tag: 'link',
props: {
rel: 'stylesheet',
type: 'text/css',
href: '/css/admin.css'
}
});
}
res.locals.footer.push({
tag: 'script',
props: {
src: `${res.baseScriptsURL}/js/admin.js`
}
});
next();
} else {
res.redirect('/admin/login');
}
});
adminRouter.get('/admin*', (req, res, next) => {
if (req.isAuthenticated()) {
routeHandler(routes, req, res, next);
} else {
next();
}
});
adminRouter.get('/admin*', async (req, res, next) => {
if (req.isAuthenticated() && req.routerState) {
// const AdminContainer = req.routerState.components[0];
// const PanelContainer = req.routerState.components[1];
//
// const {panelSettings, defaultQuery} = PanelContainer;
// const queryVariables = Object.assign({}, defaultQuery, req.query);
//
// const paginateQuery = getQueryVariables(queryVariables);
//
// const {query, variables} = AdminContainer.getQueryAndVariables(
// {
// params: req.routerState.params,
// queryVariables: {
// ...paginateQuery
// }
// },
// {
// ...panelSettings
// }
// );
// const username = req.session.passport.user;
// const user = await UserModel
// .findOne({username})
// .select({
// _id: 1,
// username: 1,
// name: 1,
// email: 1
// })
// .exec();
// const data = await graphql(
// schema.getSchema(),
// query,
// {
// isAuthenticated: true,
// user
// },
// variables
// );
// await req.store.dispatch({
// type: graphqlActionType,
// ...data
// });
res.status(200).send(getMarkup(req.store, res));
} else {
next();
}
});
export default adminRouter;
================================================
FILE: lib/server/routers/auth.js
================================================
import getDefaultFavicon from 'helpers/default-favicon';
import getMarkup from 'helpers/get-markup';
import passport from 'passport';
import routeHandler from 'helpers/route-handler';
import routes from 'routers/auth';
import {Router} from 'express';
import UserModel from '../models/user';
const authRouter = new Router();
function injectScript (req, res, next) {
res.locals.header.push({
tag: 'link',
props: {
rel: 'stylesheet',
type: 'text/css',
href: 'https://fonts.googleapis.com/css?family=Open+Sans:300,400,600,700'
}
});
if (process.env.NODE_ENV === 'production') {
res.locals.header.push({
tag: 'link',
props: {
rel: 'stylesheet',
type: 'text/css',
href: '/css/auth.css'
}
});
}
res.locals.header.push(getDefaultFavicon(res));
res.locals.footer.push({
tag: 'script',
props: {
src: `${res.baseScriptsURL}/js/auth.js`
}
});
next();
}
authRouter.get(/^\/admin\/(login|init)$/, (req, res, next) => {
if (req.isAuthenticated()) {
res.redirect('/admin');
} else {
routeHandler(routes, req, res, next);
}
});
// Logout
authRouter.get('/admin/logout', (req, res) => {
req.logout();
res.redirect('/admin/login');
});
// Register
authRouter.get('/admin/init', injectScript, async (req, res, next) => {
try {
const count = await UserModel.count().exec();
if (count === 0) {
res.status(200).send(getMarkup(req.store, res));
} else {
next();
}
} catch (error) {
next(error);
}
});
// Login
authRouter.get('/admin/login', injectScript, (req, res) => {
if (req.isAuthenticated()) {
res.redirect('/admin');
} else {
res.status(200).send(getMarkup(req.store, res));
}
});
authRouter.post('/admin/login', (req, res, next) => {
passport.authenticate('local', (err, user) => {
if (err) {
res.status(500).send({
error: 500,
message: err.message
});
} else if (!user) {
res.status(403).send({
error: 403,
message: 'Invalid username and password combination'
});
} else {
req.logIn(user, (error) => {
if (error) {
res.status(500).send({
error: 500,
message: error.message
});
} else {
res.status(200).end();
}
});
}
})(req, res, next);
});
export default authRouter;
================================================
FILE: lib/server/routers/index.js
================================================
import adminRouter from './admin';
import authRouter from './auth';
import publicRouter from './public';
export default {
adminRouter,
authRouter,
publicRouter
};
================================================
FILE: lib/server/routers/public.js
================================================
import find from 'lodash.find';
import forEach from 'lodash.foreach';
import getDefaultFavicon from 'helpers/default-favicon';
import getMarkup from 'helpers/get-markup';
import nodemailer from 'nodemailer';
import parseSettings from 'helpers/parse-settings';
import path from 'path';
import resizeImage from 'helpers/resize-image';
import routeHandler from 'helpers/route-handler';
import routes from 'routers/public';
import {graphql as graphqlActionType} from 'actions';
import {Router} from 'express';
import {graphql} from 'graphql';
import schema from '../schema';
import MediaModel from '../models/media';
import SettingModel from '../models/setting';
const publicRouter = new Router();
publicRouter.get('/api/media/resize/:mediaId/:width/:height', async (req, res, next) => {
try {
const {mediaId: id} = req.params;
let {width, height} = req.params;
width = parseInt(width, 10);
height = parseInt(height, 10);
const media = await MediaModel
.findById(id)
.select({
dimension: 1,
variations: 1,
fileName: 1,
absoluteUrl: 1
})
.exec();
if (!media) {
throw new Error('Media not found');
}
if (!media.dimension) {
throw new Error('Media file is not an image');
}
const relativePath = path.join('media', id);
const mediaPath = path.join('.', 'public', relativePath);
const originalRatio = media.dimension.width / media.dimension.height;
let resultWidth = Math.ceil(width / 100) * 100;
let resultHeight = resultWidth / originalRatio;
if (resultHeight < height) {
resultHeight = Math.ceil(height / 100) * 100;
resultWidth = resultHeight * originalRatio;
}
resultWidth = Math.round(resultWidth);
resultHeight = Math.round(resultHeight);
const filename = `${resultWidth}x${resultHeight}-${media.fileName}`;
const filePath = path.join(mediaPath, filename);
let variation;
// Check if variation already exists
forEach(media.variations, (_variation) => {
const {dimension: {width: _width, height: _height}} = _variation;
if (_width === resultWidth && _height === resultHeight) {
variation = _variation;
return false;
}
});
if (!variation) {
await resizeImage(media.absoluteUrl, filePath, {
width: resultWidth,
height: resultHeight,
quality: 100
});
variation = {
url: path.join(relativePath, filename),
absoluteUrl: path.join(mediaPath, filename),
dimension: {
width: resultWidth,
height: resultHeight
}
};
media.variations.push(variation);
await media.save();
}
res.sendFile(variation.absoluteUrl, {root: '.'});
} catch (error) {
next(error);
}
});
publicRouter.post('/send-email', async (req, res) => {
const settingsIds = [
'mailService',
'mailUser',
'mailPass',
'mailTo'
];
const settingsArr = await SettingModel
.find({
_id: {
$in: settingsIds
}
})
.exec();
const settings = parseSettings(settingsArr);
const formData = req.body;
let allSetup = true;
forEach(settingsIds, (id) => {
if (!settings[id]) {
allSetup = false;
return false;
}
});
if (allSetup) {
const transporter = nodemailer.createTransport({
service: settings.mailService,
auth: {
user: settings.mailUser,
pass: settings.mailPass
}
});
const mailOptions = {
from: formData.from,
to: settings.mailTo,
subject: formData.subject,
html: formData.message
};
try {
await transporter.sendMail(mailOptions);
res.status(200).send();
} catch (err) {
res.status(500).send('Error sending email');
}
} else {
res.status(500).send('Admin: setup not concluded');
}
});
publicRouter.use('/', (req, res, next) => {
res.locals.footer.push({
tag: 'script',
props: {
src: `${res.baseScriptsURL}/js/public.js`
}
});
res.locals.header.push({
tag: 'link',
props: {
rel: 'stylesheet',
type: 'text/css',
href: '/css/public.css'
}
});
routeHandler(routes, req, res, next);
});
publicRouter.use('/', async (req, res, next) => {
if (req.routerState) {
const settingsArr = await SettingModel
.find({
_id: {$in: ['favicon', 'webclip']}
})
.exec();
const settings = parseSettings(settingsArr);
let iconLink = getDefaultFavicon(res);
if (settings.favicon) {
const favicon = await MediaModel
.findById(settings.favicon)
.select('type url')
.exec();
if (favicon) {
iconLink = {
tag: 'link',
props: {
rel: 'icon',
type: favicon.type,
href: `${res.baseScriptsURL}/${favicon.url}`
}
};
}
}
res.locals.header.push(iconLink);
if (settings.webclip) {
const webclip = await MediaModel
.findById(settings.webclip)
.select('url')
.exec();
if (webclip) {
res.locals.header.push({
tag: 'link',
props: {
rel: 'apple-touch-icon',
href: `${res.baseScriptsURL}/${webclip.url}`
}
});
}
}
const PageContainer = req.routerState.components[0];
const {query, variables} = PageContainer.getQueryAndVariables(
{
params: req.routerState.params
}
);
const data = await graphql(
schema.getSchema(),
query,
{
isAuthenticated: false,
user: null
},
variables
);
if (data && data.data && data.data.page && data.data.page.title) {
const titleTag = find(res.locals.header, 'tag', 'title');
titleTag.content += ` - ${data.data.page.title}`;
}
await req.store.dispatch({
type: graphqlActionType,
...data
});
res.status(200).send(getMarkup(req.store, res));
} else {
next();
}
});
export default publicRouter;
================================================
FILE: lib/server/schema.js
================================================
import clone from 'lodash.clone';
import forEach from 'lodash.foreach';
import {
GraphQLObjectType,
GraphQLSchema
} from 'graphql';
import mutations from './graphql/mutations';
import queries from './graphql/queries';
import SchemaModel from './models/schema';
// import schemaEntryInputType from './graphql/types/generators/schema-entry-input';
// import schemaEntryType from './graphql/types/generators/schema-entry';
// import schemaListCountQuery from './graphql/queries/generators/schema-list-count';
// import schemaListQuery from './graphql/queries/generators/schema-list';
class SchemaManager {
constructor () {
this.init();
}
async init () {
this.queryFields = clone(queries);
this.mutationFields = clone(mutations);
const schemas = await SchemaModel.find().exec();
forEach(schemas, (schema) => {
this.processSchema(schema);
});
this.createRoot();
}
processSchema (/* schema */) {
// const type = schemaEntryType(schema);
// const inputType = schemaEntryInputType(schema);
//
// const schemaQueries = {
// ['rlx_' + schema.slug]: schemaListQuery(type, schema),
// ['rlx_' + schema.slug + '_count']: schemaListCountQuery(type, schema)
// };
//
// // TODO create mutations
//
// Object.assign(this.queryFields, schemaQueries);
}
createRoot () {
this.rootQuery = new GraphQLObjectType({
name: 'Query',
fields: () => (this.queryFields)
});
this.rootMutation = new GraphQLObjectType({
name: 'Mutation',
fields: () => (this.mutationFields)
});
}
getSchema () {
const schema = {
query: this.rootQuery
};
if (Object.keys(this.mutationFields).length) {
schema.mutation = this.rootMutation;
}
return new GraphQLSchema(schema);
}
}
export default new SchemaManager();
================================================
FILE: lib/server/shared/components/html.jsx
================================================
import React, {PropTypes} from 'react';
export default class Html extends React.Component {
static propTypes = {
locals: PropTypes.object,
props: PropTypes.any,
body: PropTypes.any
};
render () {
return (
{this.renderHeader()}
{this.renderFooter()}
);
}
renderHeader () {
if (this.props.locals && this.props.locals.header) {
return this.props.locals.header.map(this.renderTag, this);
}
}
renderFooter () {
if (this.props.locals && this.props.locals.footer) {
return this.props.locals.footer.map(this.renderTag, this);
}
}
renderTag (tag) {
const tagProps = Object.assign({}, tag.props || {});
if (tag.content) {
tagProps.dangerouslySetInnerHTML = {__html: tag.content};
}
return (
);
}
}
================================================
FILE: lib/server/shared/helpers/create-image-thumbnail.js
================================================
import path from 'path';
import sharp from 'sharp';
import {fcall, ninvoke} from 'q';
const defaultOptions = {
width: 100,
height: 100,
quality: 100
};
export default async function createImageThumbnail (imagePath, destPath, options = defaultOptions) {
const {quality, width, height} = options;
const image = await fcall(sharp, imagePath);
const metadata = await ninvoke(image, 'metadata');
const thumbnailPath = `thumbnail.${metadata.format}`;
await image
.quality(quality)
.resize(width, height)
.toFile(path.join(destPath, thumbnailPath));
return {
thumbnailPath,
metadata
};
}
================================================
FILE: lib/server/shared/helpers/default-favicon.js
================================================
export default function getDefaultIcon (res) {
return {
tag: 'link',
props: {
rel: 'shortcut icon',
type: 'image/vnd.microsoft.icon',
href: `${res.baseScriptsURL}/images/admin/favicon.ico`
}
};
}
================================================
FILE: lib/server/shared/helpers/file-mimetype.js
================================================
export default function fileMimetype (file) {
const fileData = file.file;
return fileData.substring(fileData.indexOf(':') + 1, fileData.indexOf(';'));
}
================================================
FILE: lib/server/shared/helpers/get-markup.js
================================================
import serialize from 'serialize-javascript';
import Html from 'components/html';
import React from 'react';
import {renderToString} from 'react-dom/server';
import {Provider} from 'react-redux';
import {ReduxRouter} from 'redux-router';
export default function getMarkup (store, res) {
const state = store.getState();
const initialState = serialize(state);
const markup = renderToString(
);
const htmlMarkup = renderToString(
);
return htmlMarkup;
}
================================================
FILE: lib/server/shared/helpers/get-projection.js
================================================
export default function getProjection (fieldASTs) {
return fieldASTs.selectionSet.selections.reduce((projections, selection) => {
projections[selection.name.value] = 1;
return projections;
}, {});
}
================================================
FILE: lib/server/shared/helpers/get-unique-slug.js
================================================
import slugify from 'slug';
async function check (Model, slug) {
const exists = await Model.count({slug});
return {
exists,
slug
};
}
export default async (Model, str) => {
const generatedSlug = slugify(str).toLowerCase();
let result = await check(Model, generatedSlug);
if (result.exists) {
let counter = 1;
do {
result = await check(Model, `${generatedSlug}-${counter}`);
counter++;
} while (result.exists);
}
return result.slug;
};
================================================
FILE: lib/server/shared/helpers/resize-image.js
================================================
import sharp from 'sharp';
import {fcall} from 'q';
const defaultOptions = {
width: 100,
height: 100,
quality: 100
};
export default async function resizeImage (imagePath, destPath, options = defaultOptions) {
const {quality, width, height} = options;
const image = await fcall(sharp, imagePath);
image.quality(quality).resize(width, height);
await image.toFile(destPath);
}
================================================
FILE: lib/server/shared/helpers/route-handler.js
================================================
import configureStore from 'helpers/configure-store';
import {createMemoryHistory} from 'history';
import {match, reduxReactRouter} from 'redux-router/server';
export default function routeHandler (routes, req, res, next) {
const store = configureStore(reduxReactRouter({
createHistory: createMemoryHistory,
routes
}));
const url = req.originalUrl;
store.dispatch(match(url, async (error, redirectLocation, routerState) => {
if (error) {
next(error);
} else if (redirectLocation) {
res.redirect(302, redirectLocation.pathname + redirectLocation.search);
} else if (routerState) {
req.routerState = routerState;
req.store = store;
next();
} else {
res.status(404).send('Not found');
}
}));
}
================================================
FILE: lib/server/shared/helpers/safe-html-string.js
================================================
export default function safeHtmlString (str) {
return str.replace(/<\/?[^>]+(>|$)/g, '');
}
================================================
FILE: lib/server/shared/helpers/write-file.js
================================================
import fs from 'fs';
import mkdirp from 'mkdirp';
import path from 'path';
import {nfcall} from 'q';
export default async function writeFile (file, _destPath) {
let fileData = file.file;
fileData = fileData.substring(fileData.indexOf(',') + 1);
const destPath = path.join(_destPath, file.filename);
try {
await nfcall(mkdirp, _destPath);
await nfcall(fs.writeFile, destPath, fileData, 'base64');
} catch (ex) {
throw ex;
}
return {
filename: file.filename,
size: fileData.length,
destPath
};
}
================================================
FILE: lib/shared/actions/admin-menu.js
================================================
import actionTypes from 'actions';
export function openAdminMenu () {
return {
type: actionTypes.openAdminMenu
};
}
export function closeAdminMenu () {
return {
type: actionTypes.closeAdminMenu
};
}
================================================
FILE: lib/shared/actions/colors.js
================================================
import {mutation} from 'relate-js';
export function updateColor (data) {
return mutation({
fragments: {
updateColor: {
_id: 1,
label: 1,
value: 1
}
},
variables: {
updateColor: {
data: {
value: data,
type: 'ColorInput!'
}
}
}
});
}
export function addColor (data) {
return mutation({
fragments: {
addColor: {
_id: 1,
label: 1,
value: 1
}
},
variables: {
addColor: {
data: {
value: data,
type: 'ColorInput!'
}
}
}
});
}
export function duplicateColor (id) {
return mutation({
fragments: {
duplicateColor: {
_id: 1,
label: 1,
value: 1
}
},
variables: {
duplicateColor: {
id: {
value: id,
type: 'ID!'
}
}
}
});
}
export function removeColor (id) {
return mutation({
fragments: {
removeColor: {
_id: 1
}
},
variables: {
removeColor: {
id: {
value: id,
type: 'ID!'
}
}
},
type: 'REMOVE'
});
}
================================================
FILE: lib/shared/actions/display.js
================================================
import actionTypes from 'actions';
export function changeDisplay (value) {
return {
type: actionTypes.changeDisplay,
value
};
}
================================================
FILE: lib/shared/actions/dnd.js
================================================
import actionTypes from 'actions';
export function startDragging (draggingData, dragInfo) {
return {
type: actionTypes.startDragging,
draggingData,
dragInfo
};
}
export function onDroppable (dropInfo) {
return {
type: actionTypes.onDroppable,
dropInfo
};
}
export function outDroppable (id) {
return {
type: actionTypes.outDroppable,
id
};
}
export function stopDragging () {
return {
type: actionTypes.stopDragging
};
}
================================================
FILE: lib/shared/actions/draft.js
================================================
import actionTypes from 'actions';
import request from 'helpers/request';
import {mutation} from 'relate-js';
import {fragmentToQL} from 'relax-fragments';
export function saveDraft () {
return (dispatch, getState) => {
const pageBuilder = getState().pageBuilder;
const draftInput = Object.assign({}, {
data: JSON.stringify(pageBuilder.data),
actions: JSON.stringify(pageBuilder.actions)
});
return mutation({
fragments: {
updateDraft: {
_id: {
_id: 1,
_userId: 1
}
}
},
variables: {
updateDraft: {
id: {
type: 'ID!',
value: getState().router.params.id
},
data: {
type: 'DraftInput!',
value: draftInput
}
}
}
})(dispatch, getState);
};
}
export function dropDraft (fragments, id) {
return (dispatch) => (
request({
dispatch,
type: actionTypes.dropDraft,
query: `
mutation dropDraft ($id: String!) {
dropDraft (id: $id) {
${fragmentToQL(fragments.draft)}
}
}
`,
variables: {
id
}
})
);
}
================================================
FILE: lib/shared/actions/elements.js
================================================
import actionTypes from 'actions';
import request from 'helpers/request';
export function getElementData (elementId, {query, variables}) {
return (dispatch) => request({
dispatch,
type: actionTypes.getElementData,
query,
variables,
params: {
elementId
}
});
}
================================================
FILE: lib/shared/actions/fonts.js
================================================
import actionTypes from 'actions';
import loadFontsAsync from 'helpers/load-fonts';
import request from 'helpers/request';
export function changeFontsPreviewText (value) {
return {
type: actionTypes.changeFontsPreviewText,
value
};
}
export function changeFontsPreviewLayout (value) {
return {
type: actionTypes.changeFontsPreviewLayout,
value
};
}
export function changeFontInput (tab, value) {
return {
type: actionTypes.changeFontInput,
tab,
value
};
}
export function loadFonts () {
return (dispatch, getState) => (
loadFontsAsync({
dispatch,
webfontloader: getState().fonts.data.webfontloader,
type: actionTypes.loadFonts
})
);
}
export function saveFonts () {
return (dispatch, getState) => (
request({
dispatch,
type: actionTypes.saveFonts,
query: `
mutation saveSettings ($data: [SettingInput]!) {
saveSettings (data: $data) {
_id,
value
}
}
`,
variables: {
data: [{
_id: 'fonts',
value: JSON.stringify(getState().fonts.data)
}]
}
})
);
}
export function changeFontInputAndUpdate (tab, value) {
return {
types: [
'UPDATE_FONTS_START',
'UPDATE_FONTS_SUCCESS',
'UPDATE_FONTS_ERROR'
],
payload: [changeFontInput.bind(null, tab, value), loadFonts, saveFonts],
sequence: true
};
}
function _includeCustomFont () {
return (dispatch, getState) => {
const newCustom = getState().fonts.newCustom;
if (newCustom) {
const id = newCustom.id;
const map = newCustom.files;
let css = `
@font-face {
font-family: "${newCustom.family}";
src: url('/fonts/${id}/${map.eot}');
src:
`;
if (map.woff2) {
css += `url('/fonts/${id}/${map.woff2}'), `;
}
css += `
url('/fonts/${id}/${map.woff}'),
url('/fonts/${id}/${map.ttf}');
}
`;
const styleTag = document.createElement('style');
styleTag.type = 'text/css';
document.getElementsByTagName('head')[0].appendChild(styleTag);
if (styleTag.styleSheet) {
styleTag.styleSheet.cssText = css;
} else {
styleTag.appendChild(document.createTextNode(css));
}
}
dispatch({
type: actionTypes.customFontIncluded
});
};
}
function _submitCustomFont (name, files, types) {
return (dispatch) => (
request({
dispatch,
type: actionTypes.submitCustomFont,
query: `
mutation submitCustomFont ($name: String!, $files: [UploadedFileInput]!, $types: [String]!) {
submitCustomFont (name: $name, files: $files, types: $types) {
family,
id,
files {
eot,
woff2,
woff,
ttf
}
}
}
`,
variables: {
name,
files,
types
}
})
);
}
export function submitCustomFont (name, files, types) {
return {
types: [
'SUBMIT_CUSTOM_FONT_START',
'SUBMIT_CUSTOM_FONT_SUCCESS',
'SUBMIT_CUSTOM_FONT_ERROR'
],
payload: [_submitCustomFont.bind(null, name, files, types), _includeCustomFont, loadFonts, saveFonts],
sequence: true
};
}
export function _removeCustomFont (id) {
return (dispatch) => (
request({
dispatch,
type: actionTypes.removeCustomFont,
query: `
mutation removeCustomFont ($id: String!) {
removeCustomFont (id: $id) {
id
}
}
`,
variables: {
id
}
})
);
}
export function removeCustomFont (id) {
return {
types: [
'REMOVE_CUSTOM_FONT_START',
'REMOVE_CUSTOM_FONT_SUCCESS',
'REMOVE_CUSTOM_FONT_ERROR'
],
payload: [_removeCustomFont.bind(null, id), loadFonts, saveFonts],
sequence: true
};
}
================================================
FILE: lib/shared/actions/graphql.js
================================================
import actionTypes from 'actions';
import request from 'helpers/request';
export function graphql ({query, variables}, connectors) {
return (dispatch) => request({
dispatch,
type: actionTypes.graphql,
query,
variables,
params: {
connectors
}
});
}
export function removeConnector (id) {
return {
type: actionTypes.removeConnector,
id
};
}
================================================
FILE: lib/shared/actions/index.js
================================================
export default {
// Session
authenticate: 'AUTHENTICATE',
// Admin menu
openAdminMenu: 'OPEN_ADMIN_MENU',
closeAdminMenu: 'CLOSE_ADMIN_MENU',
// Colors
updateColor: 'UPDATE_COLOR',
addColor: 'ADD_COLOR',
removeColor: 'REMOVE_COLOR',
// Display
changeDisplay: 'CHANGE_DISPLAY',
// Fonts
changeFontsPreviewText: 'CHANGE_FONTS_PREVIEW_TEXT',
changeFontsPreviewLayout: 'CHANGE_FONTS_PREVIEW_LAYOUT',
changeFontInput: 'CHANGE_FONT_INPUT',
loadFonts: 'LOAD_FONTS',
saveFonts: 'SAVE_FONTS',
submitCustomFont: 'SUBMIT_CUSTOM_FONT',
removeCustomFont: 'REMOVE_CUSTOM_FONT',
customFontIncluded: 'CUSTOM_FONT_INCLUDED',
// Media
changeMediaDisplay: 'CHANGE_MEDIA_DISPLAY',
addFilesToUpload: 'ADD_MEDIA_FILES_TO_UPLOAD',
uploadingMedia: 'UPLOADING_MEDIA',
mediaUploadSuccess: 'UPLOAD_MEDIA_SUCCESS',
mediaUploadError: 'UPLOAD_MEDIA_ERROR',
addMedia: 'ADD_MEDIA',
removeMedia: 'REMOVE_MEDIA',
removeMediaItem: 'REMOVE_MEDIA_ITEM',
// Menu
addMenu: 'ADD_MENU',
addMenuItem: 'ADD_MENU_ITEM',
moveMenuItem: 'MOVE_MENU_ITEM',
removeMenu: 'REMOVE_MENU',
// Page
getPage: 'GET_PAGE',
updatePage: 'UPDATE_PAGE',
addPage: 'ADD_PAGE',
changePageFields: 'CHANGE_FIELDS',
changePageToDefault: 'CHANGE_TO_DEFAULT',
validatePageSlug: 'VALIDATE_PAGE_SLUG',
restorePage: 'RESTORE_PAGE',
savePageFromDraft: 'SAVE_PAGE_FROM_DRAFT',
// Pages
removePage: 'REMOVE_PAGE',
duplicatePage: 'DUPLICATE_PAGE',
// Schema
changeSchemaType: 'CHANGE_SCHEMA_TYPE',
changeSchemaTitle: 'CHANGE_SCHEMA_TITLE',
schemaStepBack: 'SCHEMA_STEP_BACK',
schemaStepForward: 'SCHEMA_STEP_FORWARD',
schemaAddProperty: 'SCHEMA_ADD_PROPERTY',
schemaToggleProperty: 'SCHEMA_TOGGLE_PROPERTY',
schemaChangePropertySetting: 'SCHEMA_CHANGE_PROPERTY_SETTING',
changeSchemaToDefault: 'CHANGE_SCHEMA_TO_DEFAULT',
updateSchema: 'UPDATE_SCHEMA',
addSchema: 'ADD_SCHEMA',
restoreSchema: 'RESTORE_SCHEMA',
validateSchemaSlug: 'VALIDATE_SCHEMA_SLUG',
// Schemas
removeSchema: 'REMOVE_SCHEMA',
// Schema List
removeSchemaEntry: 'REMOVE_SCHEMA_ENTRY',
// Schema entry
addSchemaEntry: 'ADD_SCHEMA_ENTRY',
updateSchemaEntry: 'UPDATE_SCHEMA_ENTRY',
restoreSchemaEntry: 'RESTORE_SCHEMA_ENTRY',
validateSchemaEntrySlug: 'VALIDATE_SCHEMA_ENTRY_SLUG',
changeSchemaEntryFields: 'CHANGE_SCHEMA_ENTRY_FIELDS',
changeSchemaEntryProperty: 'CHANGE_SCHEMA_ENTRY_PROPERTY',
changeSchemaEntryToDefault: 'CHANGE_SCHEMA_ENTRY_TO_DEFAULT',
// Settings
changeSettingValue: 'CHANGE_SETTING_VALUE',
saveSettings: 'SAVE_SETTINGS',
// Users
removeUser: 'REMOVE_USER',
addUser: 'ADD_USER',
// DND
startDragging: 'START_DRAGGING',
onDroppable: 'ON_DROPPABLE',
outDroppable: 'OUT_DROPPABLE',
stopDragging: 'STOP_DRAGGING',
// Page builder
pbToggleEditing: 'PB_TOGGLE_EDITING',
pbSetMenuOpened: 'PB_SET_MENU_OPENED',
pbSetMenuSide: 'PB_SET_MENU_SIDE',
pbSetMenuTab: 'PB_SET_MENU_TAB',
pbOpenElementsMenu: 'PB_OPEN_ELEMENTS_MENU',
pbCloseElementsMenu: 'PB_CLOSE_ELEMENTS_MENU',
pbToggleExpandElement: 'PB_TOGGLE_EXPAND_ELEMENT',
pbExpandAll: 'PB_EXPAND_ALL',
pbCollapseAll: 'PB_COLLAPSE_ALL',
pbToggleCategory: 'PB_TOGGLE_CATEGORY',
pbOverElement: 'PB_OVER_ELEMENT',
pbOutElement: 'PB_OUT_ELEMENT',
pbSelectElement: 'PB_SELECT_ELEMENT',
pbDoAction: 'PB_DO_ACTION',
pbUndoAction: 'PB_UNDO_ACTION',
pbRedoAction: 'PB_REDO_ACTION',
pbLinkDataMode: 'PB_LINK_DATA_MODE',
pbCloseLinkDataMode: 'PB_CLOSE_LINK_DATA_MODE',
pbLinkFormDataMode: 'PB_LINK_FORM_DATA_MODE',
pbCloseLinkFormDataMode: 'PB_CLOSE_LINK_FORM_DATA_MODE',
// Symbols
makeElementSymbol: 'MAKE_ELEMENT_SYMBOL',
getSymbol: 'GET_SYMBOL',
// Elements
getElementData: 'GET_ELEMENT_DATA',
// Revisions
getRevisions: 'GET_REVISIONS',
// Draft
saveDraft: 'SAVE_DRAFT',
dropDraft: 'DROP_DRAFT',
// Tabs
removeTab: 'REMOVE_TAB',
// styles
saveStyle: 'SAVE_STYLE',
updateStyle: 'UPDATE_STYLE',
changeStyleProp: 'CHANGE_STYLE_PROP',
removeStyle: 'REMOVE_STYLE'
};
================================================
FILE: lib/shared/actions/media.js
================================================
import actionTypes from 'actions';
import request from 'helpers/request';
import {mutation} from 'relate-js';
import {fragmentToQL} from 'relax-fragments';
export function changeMediaDisplay (display) {
return {
type: actionTypes.changeMediaDisplay,
display
};
}
export function getMediaItem (fragments, id) {
return (dispatch) => (
request({
dispatch,
type: actionTypes.getMediaItem,
query: `
query mediaItem ($id: ID!) {
mediaItem (id: $id) {
${fragmentToQL(fragments.media)}
}
}
`,
variables: {
id
}
})
);
}
export function uploadMedia (fragments, file) {
const data = {
file
};
return (dispatch) => (
request({
dispatch,
type: actionTypes.addMedia,
query: `
mutation addMedia ($data: MediaInput!) {
addMedia (data: $data) {
${fragmentToQL(fragments.media)}
}
}
`,
files: [file],
variables: {
data
}
})
);
}
export function addingMedia (fileInfo) {
return {
type: actionTypes.addingMedia,
fileInfo
};
}
export function addMedia (fragments, file, fileInfo) {
return {
types: [
'ADD_MEDIA_START',
'ADD_MEDIA_SUCCESS',
'ADD_MEDIA_ERROR'
],
payload: [addingMedia.bind(null, fileInfo), uploadMedia.bind(null, fragments, file)],
sequence: true
};
}
const uploadsAtTime = 3;
let uploadsID = 0;
let uploadingNumber = 0;
let uploadQueue = [];
// Check if there are files to upload and initiates if there is
function checkUploadQueue (dispatch, getState) {
if (uploadQueue.length) {
let numberToUpload = Math.min(uploadsAtTime - uploadingNumber, uploadQueue.length);
uploadingNumber += numberToUpload;
for (numberToUpload; numberToUpload > 0; numberToUpload--) {
const file = uploadQueue.shift();
const reader = new FileReader();
reader.onload = (event) => {
dispatch({
type: actionTypes.uploadingMedia,
fileId: file.id
});
mutation({
fragments: {
addMedia: {
_id: 1,
name: 1,
fileName: 1,
type: 1,
size: 1,
filesize: 1,
dimension: {
width: 1,
height: 1
},
url: 1,
absoluteUrl: 1,
date: 1,
thumbnail: 1,
variations: 1
}
},
variables: {
addMedia: {
data: {
value: {
file: {
file: event.target.result,
filename: file.name
}
},
type: 'MediaInput!'
}
}
}
}, (result) => {
if (result.addMedia) {
dispatch({
type: actionTypes.mediaUploadSuccess,
fileId: file.id
});
} else {
dispatch({
type: actionTypes.mediaUploadError,
fileId: file.id
});
}
})(dispatch, getState)
.catch(() => {
dispatch({
type: actionTypes.mediaUploadError,
fileId: file.id
});
})
.fin(() => {
uploadingNumber--;
checkUploadQueue(dispatch, getState);
});
};
reader.readAsDataURL(file);
}
}
}
export function uploadMediaFiles (files) {
uploadQueue = uploadQueue.concat(files);
return (dispatch, getState) => {
// add to files uploading
dispatch({
type: actionTypes.addFilesToUpload,
files: files.map((file) => Object.assign(file, {id: uploadsID++}))
});
checkUploadQueue(dispatch, getState);
};
}
export function removeMediaItems (ids) {
return mutation({
fragments: {
removeMedia: {
_id: 1
}
},
variables: {
removeMedia: {
ids: {
value: ids,
type: '[ID!]'
}
}
},
type: 'REMOVE'
});
}
export function removeMediaItem (id) {
return mutation({
fragments: {
removeMediaItem: {
_id: 1
}
},
variables: {
removeMediaItem: {
id: {
value: id,
type: 'ID!'
}
}
},
type: 'REMOVE'
});
}
================================================
FILE: lib/shared/actions/menu.js
================================================
import actionTypes from 'actions';
import {pushState} from 'redux-router';
import {mutation} from 'relate-js';
export function addMenu (data, redirect = false) {
return mutation({
fragments: {
addMenu: {
_id: 1,
title: 1,
date: 1
}
},
variables: {
addMenu: {
data: {
value: data,
type: 'MenuInput!'
}
}
}
}, (result, dispatch) => {
if (redirect) {
dispatch(pushState(null, `/admin/menus/${result.addMenu._id}`));
}
});
}
export function removeMenu (id, redirect) {
return mutation({
fragments: {
removeMenu: {
_id: 1
}
},
variables: {
removeMenu: {
id: {
value: id,
type: 'ID!'
}
}
},
type: 'REMOVE'
}, (result, dispatch) => {
if (redirect) {
dispatch(pushState(null, '/admin/menus'));
}
});
}
export function updateMenuTitle (_id, title) {
return mutation({
fragments: {
updateMenu: {
_id: 1,
title: 1
}
},
variables: {
updateMenu: {
data: {
value: {_id, title},
type: 'MenuInput!'
}
}
}
});
}
export function updateMenuData (_id) {
return (dispatch, getState) => {
const data = JSON.stringify(getState().menu);
return mutation({
fragments: {
updateMenu: {
_id: 1,
data: 1
}
},
variables: {
updateMenu: {
data: {
value: {_id, data},
type: 'MenuInput!'
}
}
}
})(dispatch, getState);
};
}
export function draggedMenuItem (dragInfo, dropInfo) {
let action;
const destination = {
id: dropInfo.id,
position: dropInfo.position ? dropInfo.position : 0
};
if (dragInfo.type === 'new') {
action = {
type: actionTypes.addMenuItem,
item: dragInfo.item,
destination
};
} else if (dragInfo.type === 'move') {
action = {
type: actionTypes.moveMenuItem,
id: dragInfo.item.id,
destination
};
}
return action;
}
================================================
FILE: lib/shared/actions/page-builder.js
================================================
import actionTypes from 'actions';
import forEach from 'lodash.foreach';
import request from 'helpers/request';
import stringifyFields from 'helpers/stringify-fields';
export function toggleEditing () {
return {
type: actionTypes.pbToggleEditing
};
}
export function setMenuOpened (value) {
return {
type: actionTypes.pbSetMenuOpened,
value
};
}
export function setMenuSide (value) {
return {
type: actionTypes.pbSetMenuSide,
value
};
}
export function setMenuTab (value) {
return {
type: actionTypes.pbSetMenuTab,
value
};
}
export function openElementsMenu (options) {
return {
type: actionTypes.pbOpenElementsMenu,
options
};
}
export function closeElementsMenu () {
return {
type: actionTypes.pbCloseElementsMenu
};
}
export function toggleCategory (category) {
return {
type: actionTypes.pbToggleCategory,
category
};
}
export function toggleExpandElement (elementId) {
return {
type: actionTypes.pbToggleExpandElement,
elementId
};
}
export function collapseAll () {
return {
type: actionTypes.pbCollapseAll
};
}
export function expandAll () {
return {
type: actionTypes.pbExpandAll
};
}
export function overElement (elementId) {
return {
type: actionTypes.pbOverElement,
elementId
};
}
export function outElement (elementId) {
return {
type: actionTypes.pbOutElement,
elementId
};
}
export function selectElement (elementId) {
return {
type: actionTypes.pbSelectElement,
elementId
};
}
export function duplicateElement (elementId) {
return {
type: actionTypes.pbDoAction,
action: {
type: 'duplicate',
elementId
}
};
}
export function removeElement (elementId) {
return {
type: actionTypes.pbDoAction,
action: {
type: 'remove',
elementId
}
};
}
export function toggleElementVisibleOn (elementId, display) {
return {
type: actionTypes.pbDoAction,
action: {
type: 'changeDisplay',
elementId,
display
}
};
}
export function changeElementAnimation (elementId, property, value) {
return {
type: actionTypes.pbDoAction,
action: {
type: 'changeAnimation',
elementId,
property,
value
}
};
}
export function changeElementPosition (elementId, property, value) {
return (dispatch, getState) => {
const {display} = getState();
dispatch({
type: actionTypes.pbDoAction,
action: {
type: 'changePosition',
elementId,
property,
value,
display
}
});
};
}
export function changeElementLabel (elementId, value) {
return {
type: actionTypes.pbDoAction,
action: {
type: 'changeLabel',
elementId,
value
}
};
}
export function changeElementStyle (elementId, property, value) {
return (dispatch, getState) => {
const {display} = getState();
dispatch({
type: actionTypes.pbDoAction,
action: {
type: 'changeStyle',
elementId,
property,
value,
display
}
});
};
}
export function changeElementProperty (elementId, property, value) {
return (dispatch, getState) => {
const {display} = getState();
dispatch({
type: actionTypes.pbDoAction,
action: {
type: 'changeProp',
elementId,
property,
value,
display
}
});
};
}
export function changeElementContent (elementId, value) {
return {
type: actionTypes.pbDoAction,
action: {
type: 'changeContent',
elementId,
value
}
};
}
export function changeElementChildren (elementId, children) {
return {
type: actionTypes.pbDoAction,
action: {
type: 'changeChildren',
elementId,
children
}
};
}
export function elementAddSchemaLink (elementId, propertyId, linkElementId, action) {
return {
type: actionTypes.pbDoAction,
action: {
type: 'elementAddSchemaLink',
elementId,
propertyId,
linkElementId,
action
}
};
}
export function elementRemoveSchemaLink (elementId, propertyId, index) {
return {
type: actionTypes.pbDoAction,
action: {
type: 'elementRemoveSchemaLink',
elementId,
propertyId,
index
}
};
}
export function elementChangeSchemaLinkAction (elementId, propertyId, index, value) {
return {
type: actionTypes.pbDoAction,
action: {
type: 'elementChangeSchemaLinkAction',
elementId,
propertyId,
index,
value
}
};
}
export function undoAction () {
return {
type: actionTypes.pbUndoAction
};
}
export function redoAction () {
return {
type: actionTypes.pbRedoAction
};
}
export function addElementAt (element, destination) {
return {
type: actionTypes.pbDoAction,
action: {
type: 'new',
element,
destination
}
};
}
export function makeElementDynamic (elementId) {
return {
type: actionTypes.pbDoAction,
action: {
type: 'makeDynamic',
elementId
}
};
}
export function draggedComponent (dragInfo, dropInfo) {
const action = {
type: dragInfo.type
};
if (dragInfo.type === 'new') {
action.element = {
tag: dragInfo.element
};
} else if (dragInfo.type === 'move') {
action.source = {
id: dragInfo.id
};
}
action.destination = {
id: dropInfo.id,
position: 0
};
if (typeof dropInfo.position !== 'undefined') {
action.destination.position = dropInfo.position;
}
return {
type: actionTypes.pbDoAction,
action
};
}
export function linkFormDataMode (elementId) {
return {
type: actionTypes.pbLinkFormDataMode,
elementId
};
}
export function closeLinkFormDataMode (elementId) {
return {
type: actionTypes.pbCloseLinkFormDataMode,
elementId
};
}
export function linkDataMode (elementId) {
return {
type: actionTypes.pbLinkDataMode,
elementId
};
}
export function closeLinkDataMode () {
return {
type: actionTypes.pbCloseLinkDataMode
};
}
function extractChildren (children, data, draftData) {
forEach(children, (childId) => {
data[childId] = Object.assign({}, draftData[childId]);
if (data[childId].children && data[childId].children.constructor === Array) {
extractChildren(data[childId].children, data, draftData);
}
});
}
export function makeElementSymbol (elementId, title) {
return (dispatch, getState) => {
const symbolData = {};
const draftData = getState().draft.data.data;
symbolData.base = Object.assign({}, draftData[elementId]);
if (symbolData.base.children && symbolData.base.children.constructor === Array) {
extractChildren(symbolData.base.children, symbolData, draftData);
}
const data = {
title,
data: symbolData
};
return request({
dispatch,
type: actionTypes.makeElementSymbol,
query: `
mutation addSymbol ($data: SymbolInput!) {
addSymbol (data: $data) {
_id,
title,
data
}
}
`,
variables: {
data: stringifyFields(data, ['data'])
},
params: {
elementId
}
});
};
}
================================================
FILE: lib/shared/actions/page.js
================================================
import actionTypes from 'actions';
import request from 'helpers/request';
import {pushState} from 'redux-router';
import {mutation} from 'relate-js';
import {fragmentToQL} from 'relax-fragments';
export function updatePage (fragments, data) {
return mutation({
fragments: {
updatePage: {
_id: 1,
title: 1,
slug: 1,
updatedDate: 1
}
},
variables: {
updatePage: {
data: {
value: data,
type: 'PageInput!'
}
}
}
});
}
export function publishPage (_id) {
return mutation({
fragments: {
updatePage: {
_id: 1,
state: 1
}
},
variables: {
updatePage: {
data: {
value: {_id, state: 'published'},
type: 'PageInput!'
}
}
}
});
}
export function unpublishPage (_id) {
return mutation({
fragments: {
updatePage: {
_id: 1,
state: 1
}
},
variables: {
updatePage: {
data: {
value: {_id, state: 'draft'},
type: 'PageInput!'
}
}
}
});
}
export function updatePageTitle (_id, title) {
return mutation({
fragments: {
updatePage: {
_id: 1,
title: 1
}
},
variables: {
updatePage: {
data: {
value: {_id, title},
type: 'PageInput!'
}
}
}
});
}
export function updatePageSlug (_id, slug) {
return mutation({
fragments: {
updatePage: {
_id: 1,
slug: 1
}
},
variables: {
updatePage: {
data: {
value: {_id, slug},
type: 'PageInput!'
}
}
}
});
}
export function savePageFromDraft (fragments, publish = false) {
return (dispatch, getState) => {
const draft = getState().draft.data;
const page = getState().page.data;
const stringified = JSON.stringify(draft.data);
const pageInput = Object.assign({}, page, {
data: stringified,
state: publish ? 'published' : page.state
});
const draftInput = Object.assign({}, draft, {
data: stringified,
actions: '[]',
__v: page.__v + 1
});
return request({
dispatch,
type: actionTypes.savePageFromDraft,
query: `
mutation ($data: PageInput!, $data0: DraftInput!) {
updatePage (data: $data) {
${fragmentToQL(fragments.page)}
}
updateDraft (data: $data0) {
${fragmentToQL(fragments.draft)}
}
}
`,
variables: {
data: pageInput,
data0: draftInput
}
});
};
}
export function addPage (fragments, data, redirect = false) {
return mutation({
fragments: {
addPage: {
_id: 1,
title: 1,
state: 1,
slug: 1,
date: 1,
updatedDate: 1
}
},
variables: {
addPage: {
data: {
value: data,
type: 'PageInput!'
}
}
}
}, (result, dispatch) => {
if (redirect) {
dispatch(pushState(null, `/admin/pages/${result.addPage._id}`));
}
});
}
export function removePage (id, redirect = false) {
return mutation({
fragments: {
removePage: {
_id: 1
}
},
variables: {
removePage: {
id: {
value: id,
type: 'ID!'
}
}
},
type: 'REMOVE'
}, (result, dispatch) => {
if (redirect) {
dispatch(pushState(null, '/admin/pages'));
}
});
}
export function duplicatePage (fragments, data) {
return (dispatch) => (
request({
dispatch,
type: actionTypes.duplicatePage,
query: `
mutation duplicatePage ($data: String!) {
duplicatePage (data: $data) {
${fragmentToQL(fragments.page)}
}
}
`,
variables: {
data
}
})
);
}
export function restorePage (fragments, pageId, version) {
return (dispatch) => (
request({
dispatch,
type: actionTypes.restorePage,
query: `
mutation restorePage ($pageId: ID!, $version: Int!) {
restorePage (pageId: $pageId, version: $version) {
${fragmentToQL(fragments.page)}
}
}
`,
variables: {
pageId,
version
}
})
);
}
export function validatePageSlug ({slug, pageId}) {
return (dispatch) => (
request({
dispatch,
type: actionTypes.validatePageSlug,
query: `
query validatePageSlug ($slug: String!, $pageId: ID) {
validatePageSlug (slug: $slug, pageId: $pageId)
}
`,
variables: {
slug,
pageId
}
})
);
}
export function changePageFields (values) {
return {
type: actionTypes.changePageFields,
values
};
}
export function changePageToDefault () {
return {
type: actionTypes.changePageToDefault
};
}
================================================
FILE: lib/shared/actions/revisions.js
================================================
import {fragmentToQL} from 'relax-fragments';
import actionTypes from 'actions';
import request from 'helpers/request';
export function getRevisions (fragments, id) {
return (dispatch) => (
request({
dispatch,
type: actionTypes.getRevisions,
query: `
query revisions ($id: String!) {
revisions (id: $id) {
${fragmentToQL(fragments.revisions)}
}
}
`,
variables: {
id
}
})
);
}
================================================
FILE: lib/shared/actions/schema-entry.js
================================================
import actionTypes from 'actions';
import request from 'helpers/request';
import stringifyFields from 'helpers/stringify-fields';
import {pushState} from 'redux-router';
import {mutation} from 'relate-js';
import {fragmentToQL} from 'relax-fragments';
const stringifiableFields = ['properties', 'data'];
export function addSchemaEntry (schemaId, data, redirect = false) {
return mutation({
fragments: {
addSchemaEntry: {
_id: 1,
title: 1,
state: 1,
slug: 1,
date: 1,
updatedDate: 1
}
},
variables: {
addSchemaEntry: {
schemaId: {
value: schemaId,
type: 'ID!'
},
data: {
value: data,
type: 'SchemaEntryInput!'
}
}
}
}, (result, dispatch) => {
if (redirect) {
dispatch(pushState(null, `/admin/schemas/${schemaId}/${result.addSchemaEntry._id}`));
}
});
}
export function updateSchemaEntry (fragments, schemaId, data) {
return (dispatch) => request({
dispatch,
type: actionTypes.updateSchemaEntry,
query: `
mutation updateSchemaEntry ($schemaId: ID!, $data: SchemaEntryInput!) {
updateSchemaEntry (schemaId: $schemaId, data: $data) {
${fragmentToQL(fragments.schemaEntry)}
}
}
`,
variables: {
schemaId,
data: stringifyFields(data, stringifiableFields)
}
}).then((resultData) => resultData.updateSchemaEntry);
}
export function restoreSchemaEntry (fragments, schemaId, schemaEntryId, version) {
return (dispatch) => (
request({
dispatch,
type: actionTypes.restoreSchemaEntry,
query: `
mutation restoreSchemaEntry ($schemaId: ID!, $schemaEntryId: ID!, $version: Int!) {
restoreSchemaEntry (schemaId: $schemaId, schemaEntryId: $schemaEntryId, version: $version) {
${fragmentToQL(fragments.schemaEntry)}
}
}
`,
variables: {
schemaId,
schemaEntryId,
version
}
})
);
}
export function validateSchemaEntrySlug ({slug, schemaId, schemaEntryId}) {
return (dispatch) => (
request({
dispatch,
type: actionTypes.validateSchemaEntrySlug,
query: `
query validateSchemaEntrySlug ($slug: String!, $schemaId: ID, $schemaEntryId: ID) {
validateSchemaEntrySlug (slug: $slug, schemaId: $schemaId, schemaEntryId: $schemaEntryId)
}
`,
variables: {
slug,
schemaId,
schemaEntryId
}
})
);
}
export function changeSchemaEntryFields (values) {
return {
type: actionTypes.changeSchemaEntryFields,
values
};
}
export function changeSchemaEntryProperty (key, value) {
return {
type: actionTypes.changeSchemaEntryProperty,
key,
value
};
}
export function changeSchemaEntryToDefault () {
return {
type: actionTypes.changeSchemaEntryToDefault
};
}
================================================
FILE: lib/shared/actions/schema-list.js
================================================
import actionTypes from 'actions';
import request from 'helpers/request';
import {fragmentToQL} from 'relax-fragments';
export function removeSchemaEntry (fragments, schemaId, id) {
return (dispatch) => (
request({
dispatch,
type: actionTypes.removeSchemaEntry,
query: `
mutation removeSchemaEntry ($schemaId: ID!, $id: ID!) {
removeSchemaEntry (schemaId: $schemaId, id: $id) {
${fragmentToQL(fragments.schemaEntry)}
}
}
`,
variables: {
id,
schemaId
}
})
);
}
================================================
FILE: lib/shared/actions/schema.js
================================================
import actionTypes from 'actions';
import request from 'helpers/request';
import stringifyFields from 'helpers/stringify-fields';
import {pushState} from 'redux-router';
import {mutation} from 'relate-js';
import {fragmentToQL} from 'relax-fragments';
const stringifiableFields = ['properties', 'data'];
export function changeSchemaType (schemaType) {
return {
type: actionTypes.changeSchemaType,
schemaType
};
}
export function changeSchemaTitle (title) {
return {
type: actionTypes.changeSchemaTitle,
title
};
}
export function schemaStepBack () {
return {
type: actionTypes.schemaStepBack
};
}
export function schemaStepForward () {
return {
type: actionTypes.schemaStepForward
};
}
export function changeSchemaToDefault () {
return {
type: actionTypes.changeSchemaToDefault
};
}
export function addProperty () {
return {
type: actionTypes.schemaAddProperty
};
}
export function toggleProperty (id) {
return {
type: actionTypes.schemaToggleProperty,
id
};
}
export function changePropertySetting (id, settingId, value) {
return {
type: actionTypes.schemaChangePropertySetting,
id,
settingId,
value
};
}
export function addSchema (data, redirect = true) {
return mutation({
fragments: {
addSchema: {
_id: 1,
title: 1,
type: 1
}
},
variables: {
addSchema: {
data: {
value: stringifyFields(data, stringifiableFields),
type: 'SchemaInput!'
}
}
}
}, (result, dispatch) => {
if (redirect) {
dispatch(pushState(null, `/admin/schemas/${result.addSchema._id}`));
}
});
}
export function updateSchema (fragments, data) {
return (dispatch) => (
request({
dispatch,
type: actionTypes.updateSchema,
query: `
mutation updateSchema ($data: SchemaInput!) {
updateSchema (data: $data) {
${fragmentToQL(fragments.schema)}
}
}
`,
variables: {
data: stringifyFields(data, stringifiableFields)
}
})
);
}
export function restoreSchema (fragments, schemaId, version) {
return (dispatch) => (
request({
dispatch,
type: actionTypes.restoreSchema,
query: `
mutation restoreSchema ($schemaId: ID!, $version: Int!) {
restoreSchema (schemaId: $schemaId, version: $version) {
${fragmentToQL(fragments.schema)}
}
}
`,
variables: {
schemaId,
version
}
})
);
}
export function validateSchemaSlug ({slug, schemaId}) {
return (dispatch) => (
request({
dispatch,
type: actionTypes.validateSchemaSlug,
query: `
query validateSchemaSlug ($slug: String!, $schemaId: ID) {
validateSchemaSlug (slug: $slug, schemaId: $schemaId)
}
`,
variables: {
slug,
schemaId
}
})
);
}
================================================
FILE: lib/shared/actions/schemas.js
================================================
import actionTypes from 'actions';
import request from 'helpers/request';
import {fragmentToQL} from 'relax-fragments';
export function removeSchema (fragments, id) {
return (dispatch) => (
request({
dispatch,
type: actionTypes.removeSchema,
query: `
mutation removeSchema ($id: String!) {
removeSchema (id: $id) {
${fragmentToQL(fragments.schema)}
}
}
`,
variables: {
id
}
})
);
}
================================================
FILE: lib/shared/actions/session.js
================================================
import actionTypes from 'actions';
import request from 'helpers/request';
export function authenticate (dispatch) {
return (nextState, replaceState, callback) => {
if (dispatch) {
request({
dispatch,
type: actionTypes.authenticate,
query: `
query {
session {
userId
}
}
`
}).then(() => {
callback();
}).catch(() => {
replaceState(null, '/admin/login');
});
} else {
callback();
}
};
}
================================================
FILE: lib/shared/actions/settings.js
================================================
import actionTypes from 'actions';
import forEach from 'lodash.foreach';
import request from 'helpers/request';
import {fragmentToQL} from 'relax-fragments';
export function changeSettingValue (id, value) {
return {
type: actionTypes.changeSettingValue,
id,
value
};
}
export function saveSettings (fragments, data) {
const settings = [];
forEach(data, (value, _id) => {
settings.push({_id, value});
});
return (dispatch) => (
request({
dispatch,
type: actionTypes.saveSettings,
query: `
mutation saveSettings ($data: [SettingInput]!) {
saveSettings (data: $data) {
${fragmentToQL(fragments.settings)}
}
}
`,
variables: {
data: settings
}
})
);
}
================================================
FILE: lib/shared/actions/styles.js
================================================
import actionTypes from 'actions';
import request from 'helpers/request';
import stringifyFields from 'helpers/stringify-fields';
import {fragmentToQL} from 'relax-fragments';
const stringifiableFields = ['options', 'displayOptions'];
export function saveStyle (fragments, elementId, data) {
return (dispatch, getState) => (
request({
dispatch,
type: actionTypes.saveStyle,
query: `
mutation addStyle ($data: StyleInput!) {
addStyle (data: $data) {
${fragmentToQL(fragments.style)}
}
}
`,
variables: {
data: stringifyFields(data, stringifiableFields)
},
params: {
elementId,
display: getState().display
}
})
);
}
export function changeStyleProp (styleId, property, value) {
return (dispatch, getState) => {
const {display} = getState();
dispatch({
type: actionTypes.changeStyleProp,
styleId,
property,
value,
display
});
};
}
export function updateStyle (fragments, data) {
return (dispatch) => (
request({
dispatch,
type: actionTypes.updateStyle,
query: `
mutation updateStyle ($data: StyleInput!) {
updateStyle (data: $data) {
${fragmentToQL(fragments.style)}
}
}
`,
variables: {
data: stringifyFields(data, stringifiableFields)
}
})
);
}
export function removeStyle (styleId) {
return (dispatch) => (
request({
dispatch,
type: actionTypes.removeStyle,
query: `
mutation removeStyle ($id: ID!) {
removeStyle (_id: $id) {
_id
}
}
`,
variables: {
id: styleId
}
})
);
}
================================================
FILE: lib/shared/actions/symbols.js
================================================
import actionTypes from 'actions';
import request from 'helpers/request';
import {fragmentToQL} from 'relax-fragments';
export function getSymbol (symbolId, fragments) {
return (dispatch) => (
request({
dispatch,
type: actionTypes.getSymbol,
query: `
query symbol ($id: ID!) {
symbol (id: $id) {
${fragmentToQL(fragments.symbol)}
}
}
`,
variables: {
id: symbolId
}
})
);
}
================================================
FILE: lib/shared/actions/tabs.js
================================================
import {pushState} from 'redux-router';
import {mutation} from 'relate-js';
export function addTab (type, id) {
return mutation({
fragments: {
addTab: {
_id: 1,
type: 1,
item: {
_id: 1,
title: 1
}
}
},
variables: {
addTab: {
id: {
value: id,
type: 'ID!'
},
type: {
value: type,
type: 'String!'
}
}
}
});
}
export function removeTab (id, redirect) {
return mutation({
fragments: {
removeTab: {
_id: 1
}
},
variables: {
removeTab: {
id: {
value: id,
type: 'ID!'
}
}
},
type: 'REMOVE'
}, (result, dispatch) => {
if (redirect) {
dispatch(pushState(null, redirect));
}
});
}
================================================
FILE: lib/shared/actions/users.js
================================================
import {mutation} from 'relate-js';
export function removeUser (id) {
return mutation({
fragments: {
removeUser: {
_id: 1
}
},
variables: {
removeUser: {
id: {
value: id,
type: 'ID!'
}
}
},
type: 'REMOVE'
});
}
export function addUser (fragments, data) {
return mutation({
fragments: {
addUser: fragments.users
},
variables: {
addUser: {
data: {
value: data,
type: 'UserInput!'
}
}
}
});
}
================================================
FILE: lib/shared/components/a.jsx
================================================
import qs from 'query-string';
import Component from 'components/component';
import React from 'react';
import {Link} from 'react-router';
export default class A extends Component {
static propTypes = {
href: React.PropTypes.string.isRequired,
children: React.PropTypes.node,
onClick: React.PropTypes.func,
afterClick: React.PropTypes.func
};
onClick (event) {
if (this.props.onClick) {
this.props.onClick(event);
}
if (this.props.afterClick) {
this.props.afterClick(event);
}
}
render () {
const {href, ...tagProps} = this.props;
const urlAndQuery = href && href.split('?');
let url;
let query;
if (urlAndQuery) {
url = urlAndQuery[0];
query = qs.parse(urlAndQuery[1]);
}
return (
{this.props.children}
);
}
}
================================================
FILE: lib/shared/components/animate-props.jsx
================================================
import velocity from 'velocity-animate';
import Component from 'components/component';
import React from 'react';
import {findDOMNode} from 'react-dom';
export default class AnimateProps extends Component {
static propTypes = {
props: React.PropTypes.object,
options: React.PropTypes.object,
children: React.PropTypes.node
};
static defaultProps = {
props: {},
options: {}
};
componentDidMount () {
const dom = findDOMNode(this);
velocity(dom, this.props.props, this.props.options);
}
render () {
return this.props.children;
}
}
================================================
FILE: lib/shared/components/animate.jsx
================================================
import velocity from 'velocity-animate';
import Component from 'components/component';
import {PropTypes} from 'react';
import {findDOMNode} from 'react-dom';
export default class Animate extends Component {
static propTypes = {
transition: PropTypes.string,
duration: PropTypes.number,
children: PropTypes.node,
options: PropTypes.object,
initial: PropTypes.bool
};
static defaultProps = {
transition: 'slideUpIn',
duration: 400,
options: {},
initial: true
};
componentDidMount () {
if (this.props.initial) {
this.makeTransition(this.props);
}
}
componentWillReceiveProps (nextProps) {
if (this.props.transition !== nextProps.transition) {
this.makeTransition(nextProps);
}
}
makeTransition (props) {
const dom = findDOMNode(this);
const transition = `transition.${props.transition}`;
velocity(dom, transition, Object.assign({
duration: props.duration,
display: null
}, props.options));
}
render () {
return this.props.children;
}
}
================================================
FILE: lib/shared/components/background-image.jsx
================================================
import utils from 'helpers/utils';
import Component from 'components/component';
import React, {PropTypes} from 'react';
import {findDOMNode} from 'react-dom';
import MediaImage from './image';
export default class BackgroundImage extends Component {
static propTypes = {
backgroundImage: PropTypes.string.isRequired,
repeat: PropTypes.string.isRequired,
vertical: PropTypes.number,
horizontal: PropTypes.number,
opacity: PropTypes.number
};
static defaultProps = {
backgroundImage: '',
repeat: 'no-repeat',
vertical: 50,
horizontal: 50,
opacity: 100
};
getInitState () {
return {
mounted: false
};
}
componentDidMount () {
this.resize();
}
componentDidUpdate () {
this.resize();
}
resize () {
const dom = findDOMNode(this);
const rect = dom.getBoundingClientRect();
const width = Math.round(rect.right - rect.left);
const height = Math.round(rect.bottom - rect.top);
if (this.state.width !== width || this.state.height !== height) {
this.setState({
mounted: true,
width,
height
});
}
}
render () {
if (this.state.mounted && this.props.backgroundImage && this.props.backgroundImage !== '') {
let result;
const style = {
position: 'absolute',
top: 0, left: 0, right: 0, bottom: 0,
overflow: 'hidden',
opacity: this.props.opacity / 100
};
if (this.props.repeat === 'no-repeat') {
const imageStyle = {
position: 'relative',
minWidth: '100%',
minHeight: '100%',
maxWidth: 'none'
};
imageStyle.top = this.state.height * (this.props.vertical / 100);
imageStyle.left = this.state.width * (this.props.horizontal / 100);
utils.translate(imageStyle, `${-this.props.horizontal}%`, `${-this.props.vertical}%`);
result = (
);
} else {
style.backgroundImage = `url("${utils.getBestImageUrl(this.props.backgroundImage)}")`;
style.backgroundRepeat = this.props.repeat;
style.backgroundPosition = `${-this.props.horizontal}% ${-this.props.vertical}%`;
result = ;
}
return result;
}
return ;
}
}
================================================
FILE: lib/shared/components/button/index.jsx
================================================
import cx from 'classnames';
import forEach from 'lodash.foreach';
import Component from 'components/component';
import React, {PropTypes} from 'react';
import styles from './index.less';
export default class Button extends Component {
static propTypes = {
children: PropTypes.node.isRequired,
onClick: PropTypes.func.isRequired,
className: PropTypes.string,
style: PropTypes.object,
primary: PropTypes.bool,
full: PropTypes.bool,
big: PropTypes.bool,
noBackground: PropTypes.bool
};
static defaultProps = {
primary: true,
full: false,
big: false,
noBackground: false
};
render () {
const {onClick, className, ...classes} = this.props;
let resultClassName = cx(styles.button, className);
forEach(classes, (value, key) => {
if (styles[key] && value) {
resultClassName = cx(resultClassName, styles[key]);
}
});
return (
);
}
}
================================================
FILE: lib/shared/components/button/index.less
================================================
@import '~styles/colors.less';
.button {
cursor: pointer;
padding: 8px 15px;
text-decoration: none;
font-size: 12px;
line-height: 15px;
display: inline-block;
vertical-align: top;
border-radius: 3px;
outline: 0;
border: 0;
}
.full {
display: block;
text-align: center;
width: 100%;
margin-right: 0;
text-transform: uppercase;
font-size: 10px;
}
.primary {
background-color: @primary;
color: #ffffff;
&:hover {
background-color: @primaryDarker;
}
}
.alert {
background-color: @alert;
color: #ffffff;
}
.big {
font-size: 12px;
padding: 12px 15px;
}
.noBackground {
background-color: transparent;
text-transform: uppercase;
&.primary {
color: @primary;
font-size: 16px;
}
&.alert {
color: @alert;
font-size: 16px;
}
&.grey {
color: @adminText;
font-size: 16px;
}
&:hover {
background-color: transparent;
}
}
================================================
FILE: lib/shared/components/component.jsx
================================================
import React from 'react';
export default class BaseComponent extends React.Component {
constructor (props, context) {
super(props, context);
this.state = this.getInitState ? this.getInitState() : {};
this.init && this.init();
}
isClient () {
return typeof document !== 'undefined';
}
}
================================================
FILE: lib/shared/components/dnd/draggable/draggable.jsx
================================================
import cx from 'classnames';
import Component from 'components/component';
import React, {PropTypes} from 'react';
import {findDOMNode} from 'react-dom';
const LEFT_BUTTON = 0;
export default class Draggable extends Component {
static propTypes = {
dndActions: PropTypes.object.isRequired,
type: PropTypes.string.isRequired,
dragInfo: PropTypes.object.isRequired,
onStartDrag: PropTypes.func,
droppableOn: PropTypes.string,
onClick: PropTypes.func,
disabled: PropTypes.bool,
children: PropTypes.node
};
constructor (props, children) {
super(props, children);
this.onMouseUpListener = this.onMouseUp.bind(this);
this.onMouseMoveListener = this.onMouseMove.bind(this);
}
onMouseUp () {
document.removeEventListener('mouseup', this.onMouseUpListener);
document.removeEventListener('mousemove', this.onMouseMoveListener);
}
onMouseMove (event) {
event.preventDefault();
this.onMouseUp();
const element = findDOMNode(this);
const elementOffset = element.getBoundingClientRect();
const width = Math.round(elementOffset.right - elementOffset.left);
const {startDragging} = this.props.dndActions;
// Dragging data
const draggingData = {
children: this.props.children,
elementOffset,
elementWidth: width,
mouseX: event.pageX,
mouseY: event.pageY,
type: this.props.type
};
if (this.props.droppableOn) {
draggingData.droppableOn = this.props.droppableOn;
}
startDragging(draggingData, this.props.dragInfo);
this.props.onStartDrag && this.props.onStartDrag();
}
onMouseDown (event) {
if (event.button === LEFT_BUTTON) {
const draggable = !this.props.disabled;
event.stopPropagation();
if (draggable) {
document.addEventListener('mouseup', this.onMouseUpListener);
document.addEventListener('mousemove', this.onMouseMoveListener);
}
}
}
render () {
const props = {
className: cx(this.props.children.props.className, 'draggable'),
draggable: 'false',
onMouseDown: this.onMouseDown.bind(this)
};
if (this.props.onClick) {
props.onClick = this.props.onClick;
}
return React.cloneElement(this.props.children, props);
}
}
================================================
FILE: lib/shared/components/dnd/draggable/index.js
================================================
import * as dndActions from 'actions/dnd';
import Component from 'components/component';
import React from 'react';
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
import Draggable from './draggable';
@connect(
() => ({}),
(dispatch) => ({
dndActions: bindActionCreators(dndActions, dispatch)
})
)
export default class DraggableContainer extends Component {
render () {
return ;
}
}
================================================
FILE: lib/shared/components/dnd/dragger/dragger.jsx
================================================
import cx from 'classnames';
import velocity from 'velocity-animate';
import Component from 'components/component';
import Portal from 'components/portal';
import React, {PropTypes} from 'react';
import {findDOMNode} from 'react-dom';
import styles from './dragger.less';
export default class Dragger extends Component {
static propTypes = {
dndActions: PropTypes.object.isRequired,
onStopDrag: PropTypes.func.isRequired,
offset: PropTypes.object,
draggingData: PropTypes.object,
dragInfo: PropTypes.object,
dropInfo: PropTypes.object,
shadow: PropTypes.bool
};
static defaultProps = {
shadow: true
};
getInitState () {
const {draggingData, offset} = this.props;
return {
top: draggingData.elementOffset.top + (offset && offset.top ? offset.top : 0),
left: draggingData.elementOffset.left + (offset && offset.left ? offset.left : 0)
};
}
componentDidMount () {
const {draggingData} = this.props;
this.onMouseUpListener = this.onMouseUp.bind(this);
this.onMouseMoveListener = this.onMouseMove.bind(this);
const node = findDOMNode(this.refs.dragger);
const relativeX = draggingData.mouseX - draggingData.elementOffset.left;
const relativeY = draggingData.mouseY - draggingData.elementOffset.top;
node.style.transformOrigin = `${relativeX}px ${relativeY}px`;
velocity(node, {
scaleX: '0.5',
scaleY: '0.5',
opacity: '0.7'
}, {duration: 500, easing: 'easeOutExpo'});
document.addEventListener('mouseup', this.onMouseUpListener);
document.addEventListener('mousemove', this.onMouseMoveListener);
}
onMouseUp () {
const {dragInfo, dropInfo, onStopDrag, dndActions} = this.props;
document.removeEventListener('mouseup', this.onMouseUpListener);
document.removeEventListener('mousemove', this.onMouseMoveListener);
onStopDrag && dragInfo && dropInfo && onStopDrag(dragInfo, dropInfo);
dndActions.stopDragging();
}
onMouseMove (event) {
const {draggingData} = this.props;
event.preventDefault();
const deltaX = event.pageX - draggingData.mouseX + draggingData.elementOffset.left;
const deltaY = event.pageY - draggingData.mouseY + draggingData.elementOffset.top;
this.setState({
top: deltaY + (this.props.offset && this.props.offset.top ? this.props.offset.top : 0),
left: deltaX + (this.props.offset && this.props.offset.left ? this.props.offset.left : 0)
});
}
render () {
const {draggingData, shadow} = this.props;
const style = {
width: `${draggingData.elementWidth}px`,
top: `${this.state.top}px`,
left: `${this.state.left}px`
};
return (
{draggingData.children}
);
}
}
================================================
FILE: lib/shared/components/dnd/dragger/dragger.less
================================================
.root {
position: fixed;
pointer-events: none;
z-index: 20;
}
.shadow {
box-shadow: 0px 0px 20px 0px rgba(0, 0, 0, 0.5);
}
================================================
FILE: lib/shared/components/dnd/dragger/index.js
================================================
import * as dndActions from 'actions/dnd';
import Component from 'components/component';
import React from 'react';
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
import Dragger from './dragger';
@connect(
(state) => ({
draggingData: state.dnd.draggingData,
dragInfo: state.dnd.dragInfo,
dropInfo: state.dnd.dropInfo
}),
(dispatch) => ({
dndActions: bindActionCreators(dndActions, dispatch)
})
)
export default class DraggerContainer extends Component {
render () {
return ;
}
}
================================================
FILE: lib/shared/components/dnd/droppable/add-ballon.jsx
================================================
import cx from 'classnames';
import Component from 'components/component';
import React, {PropTypes} from 'react';
import {findDOMNode} from 'react-dom';
import styles from './add-ballon.less';
export default class AddBallon extends Component {
static propTypes = {
vertical: PropTypes.bool,
active: PropTypes.bool,
position: PropTypes.number.isRequired,
onClick: PropTypes.func.isRequired
};
onClick () {
const {onClick, position} = this.props;
onClick(position, findDOMNode(this.refs.marker));
}
render () {
const {vertical, active} = this.props;
return (
);
}
}
================================================
FILE: lib/shared/components/dnd/droppable/add-ballon.less
================================================
@import '~styles/colors.less';
.root {
position: relative;
width: 100%;
height: 0;
z-index: 3;
text-align: left;
left: 0;
}
.marker {
position: absolute;
display: inline-block;
left: -15px;
top: -15px;
cursor: pointer;
width: 30px;
height: 30px;
opacity: 0.9;
transform: scale(0.3);
transition: all 0.2s ease-out;
&:before {
content: ' ';
position: absolute;
top: -5px;
left: -5px;
width: 40px;
height: 40px;
background-color: rgba(255, 255, 255, 0.4);
border-radius: 50%;
opacity: 1;
}
}
.circle {
width: 30px;
height: 30px;
background-color: #131618;
display: inline-block;
position: relative;
text-align: center;
border-radius: 50%;
:global i {
opacity: 0;
text-align: center;
color: #eeeeee;
font-size: 14px;
line-height: 30px;
}
}
.triangle {
position: absolute;
background-color: #131618;
text-align: left;
cursor: pointer;
opacity: 0;
position: absolute;
left: 16px;
top: 3px;
}
.triangle:before,
.triangle:after {
content: '';
position: absolute;
background-color: inherit;
}
.triangle,
.triangle:before,
.triangle:after {
width: 16px;
height: 16px;
border-top-right-radius: 30%;
}
.triangle {
transform: rotate(-90deg) skewX(-30deg) scale(1,.866);
}
.triangle:before {
transform: rotate(-135deg) skewX(-45deg) scale(1.414,.707) translate(0,-50%);
}
.triangle:after {
transform: rotate(135deg) skewY(-45deg) scale(.707,1.414) translate(50%);
}
.root {
&:hover, &.active {
&:before {
content: ' ';
position: absolute;
display: block;
top: -1px;
bottom: -1px;
width: 100%;
background-color: @primary;
}
.marker {
transform: scale(1);
opacity: 1;
&:before {
opacity: 0;
transform: scale(1.1, 1.1);
}
}
.circle, .triangle {
background-color: @primary;
:global i {
color: #ffffff;
opacity: 1;
}
}
.triangle {
opacity: 1;
}
}
&.inverted {
.marker {
left: 0px;
.triangle {
transform: rotate(90deg) skewX(-30deg) scale(1,.866);
left: -2px;
top: 11px;
}
}
}
&.vertical {
width: 0;
height: 100%;
left: auto;
.triangle {
transform: rotate(-0deg) skewX(-30deg) scale(1,.866);
left: 11px;
top: 16px;
}
.marker {
left: -15px;
top: -15px;
width: 30px;
height: 30px;
}
&:hover, &.active {
&:before {
top: auto;
bottom: auto;
height: 100%;
left: -1px;
right: -1px;
width: auto;
}
}
}
}
================================================
FILE: lib/shared/components/dnd/droppable/droppable.jsx
================================================
import bind from 'decorators/bind';
import cx from 'classnames';
import forEach from 'lodash.foreach';
import AnimateProps from 'components/animate-props';
import Component from 'components/component';
import React, {PropTypes} from 'react';
import {findDOMNode} from 'react-dom';
import styles from './droppable.less';
import AddBallon from './add-ballon';
import Marker from './marker';
export default class Droppable extends Component {
static propTypes = {
dndActions: PropTypes.object.isRequired,
dragging: PropTypes.bool.isRequired,
activeDropInfo: PropTypes.any.isRequired,
activeDragInfo: PropTypes.any.isRequired,
dropInfo: PropTypes.object.isRequired,
minHeight: PropTypes.number.isRequired,
minWidth: PropTypes.number.isRequired,
children: PropTypes.node,
className: PropTypes.string,
style: PropTypes.object,
draggingData: PropTypes.object.isRequired,
accepts: PropTypes.any,
rejects: PropTypes.any,
type: PropTypes.string,
showMarks: PropTypes.bool.isRequired,
isActive: PropTypes.bool.isRequired,
orientation: PropTypes.string.isRequired,
elementsMenuSpot: PropTypes.string,
selectedId: PropTypes.string,
openElementsMenu: PropTypes.func.isRequired,
placeholder: PropTypes.bool,
hidePlaceholder: PropTypes.bool,
Placeholder: PropTypes.object,
placeholderRender: PropTypes.func
};
static contextTypes = {
dropBlock: PropTypes.bool
};
static childContextTypes = {
dropHighlight: PropTypes.string.isRequired,
dropBlock: PropTypes.bool
};
static defaultProps = {
orientation: 'vertical',
minHeight: 150,
minWidth: 50
};
getInitState () {
return {
closeToMargin: false
};
}
getChildContext () {
const {dragging} = this.props;
const childContext = {
dropHighlight: 'none'
};
if (dragging) {
if (this.droppableHere()) {
if (this.props.orientation && this.props.orientation === 'horizontal') {
childContext.dropHighlight = 'horizontal';
} else {
childContext.dropHighlight = 'vertical';
}
} else if (this.draggingSelf()) {
childContext.dropBlock = true;
}
}
return childContext;
}
componentWillReceiveProps () {
const containerRect = findDOMNode(this).getBoundingClientRect();
if (containerRect.left < 40) {
if (!this.state.closeToMargin) {
this.setState({
closeToMargin: true
});
}
} else if (this.state.closeToMargin) {
this.setState({
closeToMargin: false
});
}
}
@bind
onMouseEnter () {
if (!this.props.isActive) {
const order = this.hasChildren();
this.setState({
order
});
if (!order) {
const {onDroppable} = this.props.dndActions;
onDroppable(this.props.dropInfo);
}
}
}
@bind
onMouseLeave () {
if (this.props.isActive) {
const {dropInfo} = this.props;
const {outDroppable} = this.props.dndActions;
outDroppable(dropInfo.id);
}
}
hasChildren () {
const children = this.props.children;
let _hasChildren = false;
if (children) {
if (children instanceof Array) {
_hasChildren = children.length > 0;
} else if (children instanceof Object) {
_hasChildren = true;
}
}
return _hasChildren;
}
draggingSelf () {
const {dropInfo, activeDragInfo} = this.props;
return dropInfo.id && activeDragInfo.id && dropInfo.id === activeDragInfo.id;
}
droppableHere () {
const {draggingData, accepts, rejects, type} = this.props;
let is = true;
const dropBlock = this.context.dropBlock;
if (this.draggingSelf() || dropBlock) {
return false;
}
// Droppable restrictions
if (accepts) {
if (accepts !== 'any' && accepts !== draggingData.type) {
is = false;
}
} else if (rejects) {
if (rejects === 'any' || rejects === draggingData.type) {
is = false;
}
}
// Dragging restrictions
if (is && draggingData.droppableOn) {
if (draggingData.droppableOn !== 'any' && type !== draggingData.droppableOn) {
is = false;
}
}
return is;
}
getEvents (droppableHere) {
const {dragging} = this.props;
if (dragging && droppableHere) {
return {
onMouseOver: this.onMouseEnter,
onMouseLeave: this.onMouseLeave
};
}
}
addSpotClick (position, dom) {
this.props.openElementsMenu({
targetId: this.props.dropInfo.id || 'body',
targetType: this.props.type,
targetPosition: position,
container: dom || this.refs.spot0,
accepts: this.props.accepts,
rejects: this.props.rejects
});
}
render () {
const {dragging, minHeight, minWidth, className, style, showMarks, isActive, hidePlaceholder} = this.props;
const droppableHere = this.droppableHere();
let children = this.props.children;
const hasChildren = this.hasChildren();
// style
const inlineStyle = Object.assign({}, style || {});
if (!hasChildren && (!hidePlaceholder || hidePlaceholder && this.droppableHere())) {
inlineStyle.minHeight = minHeight;
inlineStyle.minWidth = minWidth;
}
// children
if (hasChildren && dragging && droppableHere) {
children = this.renderDropMarkers(children);
} else if (hasChildren && !dragging && showMarks) {
children = this.renderAddMarkers(children);
}
return (
{hasChildren ? children : this.renderPlaceholder()}
);
}
renderDropMarkers (children) {
const {isActive, activeDropInfo, dropInfo, activeDragInfo, orientation, dndActions} = this.props;
const isDraggingParent = activeDragInfo.parentId === dropInfo.id;
const tempChildren = [];
if (!isDraggingParent || activeDragInfo.positionInParent !== 0) {
tempChildren.push(
);
}
forEach(children, (child, index) => {
tempChildren.push(child);
if (!isDraggingParent ||
activeDragInfo.positionInParent !== index &&
activeDragInfo.positionInParent !== index + 1) {
tempChildren.push((
));
}
});
return tempChildren;
}
renderAddMarkers (children) {
const tempChildren = [
this.renderMark(0)
];
forEach(children, (child, index) => {
tempChildren.push(child);
tempChildren.push(this.renderMark(index + 1));
});
return tempChildren;
}
renderMark (position) {
const {elementsMenuSpot, selectedId, orientation, dropInfo} = this.props;
const vertical = orientation === 'horizontal';
const active = elementsMenuSpot === position && selectedId === dropInfo.id;
return (
);
}
renderPlaceholder () {
const {placeholder, hidePlaceholder, Placeholder, isActive, placeholderRender} = this.props;
if (placeholder && (!hidePlaceholder || hidePlaceholder && this.droppableHere())) {
let result;
const customProps = {
spotClick: ::this.addSpotClick,
isActive
};
if (Placeholder) {
result = (
);
} else if (placeholderRender) {
result = placeholderRender(customProps);
} else {
result = this.renderDefaultPlaceholder();
}
return result;
}
}
renderDefaultPlaceholder () {
let result;
if (this.props.isActive) {
const props = {
scaleX: '150%',
scaleY: '150%'
};
const options = {
duration: 600,
loop: true
};
result = (
add_circle
);
} else {
const addSpotClick = this.addSpotClick.bind(this, 0, null);
result = (
Drop elements here or
click to add
);
}
return (
{result}
);
}
}
================================================
FILE: lib/shared/components/dnd/droppable/droppable.less
================================================
================================================
FILE: lib/shared/components/dnd/droppable/index.js
================================================
import * as dndActions from 'actions/dnd';
import Component from 'components/component';
import React, {PropTypes} from 'react';
import {openElementsMenu} from 'actions/page-builder';
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
import Droppable from './droppable';
@connect(
(state, props) => ({
elementsMenuSpot: state.pageBuilder.elementsMenuSpot,
selectedId: state.pageBuilder.selectedId,
dragging: state.dnd.dragging,
activeDropInfo: state.dnd.dropInfo,
activeDragInfo: state.dnd.dragInfo,
draggingData: state.dnd.draggingData,
showMarks: props.showMarks !== false && // TODO split this into variables
state.router.location.query.build &&
state.pageBuilder.editing &&
!state.pageBuilder.linkingData &&
(props.dropInfo.id === 'body' ||
(state.pageBuilder.selectedParent === props.dropInfo.id &&
state.pageBuilder.selectedElement &&
(!state.pageBuilder.selectedElement.children ||
state.pageBuilder.selectedElement.children.constructor !== Array)) ||
(state.pageBuilder.selectedId === props.dropInfo.id)),
isActive: state.dnd.dragging && state.dnd.dropInfo.id === props.dropInfo.id
}),
(dispatch) => ({
dndActions: bindActionCreators(dndActions, dispatch),
...bindActionCreators({openElementsMenu}, dispatch)
})
)
export default class DroppableContainer extends Component {
static propTypes = {
dropInfo: PropTypes.object.isRequired
};
render () {
return ;
}
}
================================================
FILE: lib/shared/components/dnd/droppable/marker.jsx
================================================
import cx from 'classnames';
import utils from 'helpers/utils';
import Component from 'components/component';
import React, {PropTypes} from 'react';
import {findDOMNode} from 'react-dom';
import styles from './marker.less';
export default class Marker extends Component {
static propTypes = {
dndActions: PropTypes.object.isRequired,
report: PropTypes.object.isRequired,
active: PropTypes.bool,
orientation: PropTypes.string
};
getInitState () {
return {
visible: false
};
}
componentDidMount () {
this.onMouseMoveListener = ::this.onMouseMove;
document.addEventListener('mousemove', this.onMouseMoveListener);
}
componentWillUnmount () {
document.removeEventListener('mousemove', this.onMouseMoveListener);
}
onMouseMove (event) {
const elementOffset = utils.getOffsetRect(findDOMNode(this));
const distance = Math.abs(elementOffset.top - event.pageY);
if (distance < 100 && !this.state.visible) {
this.setState({
visible: true
});
document.removeEventListener('mousemove', this.onMouseMoveListener);
}
}
onMouseEnter () {
const {onDroppable} = this.props.dndActions;
onDroppable(this.props.report, this.props.orientation);
}
onMouseLeave () {
const {outDroppable} = this.props.dndActions;
outDroppable(this.props.report.id);
}
render () {
const {orientation, active} = this.props;
return (
);
}
}
================================================
FILE: lib/shared/components/dnd/droppable/marker.less
================================================
@import '~styles/colors.less';
.marker {
background-color: @primary;
opacity: 0.2;
margin-bottom: 0px;
transition: all 0.3s ease-out;
}
.vertical {
height: 0;
&.visible {
height: 8px;
margin-bottom: 1px;
}
&.active {
height: 12px;
}
}
.horizontal {
display: table-cell;
width: 0;
&.visible {
width: 7px;
}
&.active {
width: 10px;
}
}
.active {
background-color: #33CC33;
opacity: 0.7;
}
================================================
FILE: lib/shared/components/image.jsx
================================================
import utils from 'helpers/utils';
import Component from 'components/component';
import React, {PropTypes} from 'react';
export default class Image extends Component {
static propTypes = {
id: PropTypes.string.isRequired,
width: PropTypes.number,
height: PropTypes.number
};
static defaultProps = {
width: 200,
height: 0
};
render () {
const {id, width, height, ...htmlProps} = this.props;
let result = null;
if (id) {
const url = utils.getBestImageUrl(id, width, height);
result = (
);
}
return result;
}
}
================================================
FILE: lib/shared/components/medium-editor/index.jsx
================================================
import './index.less';
import Component from 'components/component';
import MediumEditor from 'medium-editor';
import React from 'react';
import {findDOMNode} from 'react-dom';
export default class MediumEditorElement extends Component {
static propTypes = {
tag: React.PropTypes.string,
className: React.PropTypes.string,
value: React.PropTypes.string.isRequired,
onChange: React.PropTypes.func.isRequired,
options: React.PropTypes.obj
};
static defaultProps = {
tag: 'div'
};
getInitState () {
this.currentValue = this.props.value;
return {
value: this.props.value
};
}
componentDidMount () {
this.medium = new MediumEditor(findDOMNode(this), this.props.options);
this.medium.subscribe('editableInput', this.onChange.bind(this));
}
componentWillReceiveProps (nextProps) {
if (this.currentValue !== nextProps.value) {
this.currentValue = nextProps.value;
this.setState({
value: nextProps.value
});
}
}
componentWillUnmount () {
this.medium.destroy();
}
onChange () {
const value = findDOMNode(this).innerHTML;
this.currentValue = value;
this.props.onChange && this.props.onChange(value);
}
render () {
return (
);
}
}
================================================
FILE: lib/shared/components/medium-editor/index.less
================================================
@import '~styles/colors.less';
:global {
@-webkit-keyframes medium-editor-image-loading {
0% {
-webkit-transform: scale(0);
transform: scale(0); }
100% {
-webkit-transform: scale(1);
transform: scale(1); } }
@keyframes medium-editor-image-loading {
0% {
-webkit-transform: scale(0);
transform: scale(0); }
100% {
-webkit-transform: scale(1);
transform: scale(1); } }
@-webkit-keyframes medium-editor-pop-upwards {
0% {
opacity: 0;
-webkit-transform: matrix(0.97, 0, 0, 1, 0, 12);
transform: matrix(0.97, 0, 0, 1, 0, 12); }
20% {
opacity: .7;
-webkit-transform: matrix(0.99, 0, 0, 1, 0, 2);
transform: matrix(0.99, 0, 0, 1, 0, 2); }
40% {
opacity: 1;
-webkit-transform: matrix(1, 0, 0, 1, 0, -1);
transform: matrix(1, 0, 0, 1, 0, -1); }
100% {
-webkit-transform: matrix(1, 0, 0, 1, 0, 0);
transform: matrix(1, 0, 0, 1, 0, 0); } }
@keyframes medium-editor-pop-upwards {
0% {
opacity: 0;
-webkit-transform: matrix(0.97, 0, 0, 1, 0, 12);
transform: matrix(0.97, 0, 0, 1, 0, 12); }
20% {
opacity: .7;
-webkit-transform: matrix(0.99, 0, 0, 1, 0, 2);
transform: matrix(0.99, 0, 0, 1, 0, 2); }
40% {
opacity: 1;
-webkit-transform: matrix(1, 0, 0, 1, 0, -1);
transform: matrix(1, 0, 0, 1, 0, -1); }
100% {
-webkit-transform: matrix(1, 0, 0, 1, 0, 0);
transform: matrix(1, 0, 0, 1, 0, 0); } }
.medium-editor-anchor-preview {
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
font-size: 16px;
left: 0;
line-height: 1.4;
max-width: 280px;
position: absolute;
text-align: center;
top: 0;
word-break: break-all;
word-wrap: break-word;
visibility: hidden;
z-index: 2000; }
.medium-editor-anchor-preview a {
color: #fff;
display: inline-block;
margin: 5px 5px 10px; }
.medium-editor-anchor-preview-active {
visibility: visible; }
.medium-editor-dragover {
background: #ddd; }
.medium-editor-image-loading {
-webkit-animation: medium-editor-image-loading 1s infinite ease-in-out;
animation: medium-editor-image-loading 1s infinite ease-in-out;
background-color: #333;
border-radius: 100%;
display: inline-block;
height: 40px;
width: 40px; }
.medium-editor-placeholder {
position: relative; }
.medium-editor-placeholder:after {
content: attr(data-placeholder) !important;
font-style: italic;
left: 0;
position: absolute;
top: 0;
white-space: pre; }
.medium-toolbar-arrow-under:after, .medium-toolbar-arrow-over:before {
border-style: solid;
content: '';
display: block;
height: 0;
left: 50%;
margin-left: -8px;
position: absolute;
width: 0; }
.medium-toolbar-arrow-under:after {
border-width: 8px 8px 0 8px; }
.medium-toolbar-arrow-over:before {
border-width: 0 8px 8px 8px;
top: -8px; }
.medium-editor-toolbar {
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
font-size: 16px;
left: 0;
position: absolute;
top: 0;
visibility: hidden;
z-index: 2000; }
.medium-editor-toolbar ul {
margin: 0;
padding: 0; }
.medium-editor-toolbar li {
float: left;
list-style: none;
margin: 0;
padding: 0; }
.medium-editor-toolbar li button {
box-sizing: border-box;
cursor: pointer;
display: block;
font-size: 14px;
line-height: 1.33;
margin: 0;
padding: 15px;
text-decoration: none; }
.medium-editor-toolbar li button:focus {
outline: none; }
.medium-editor-toolbar li .medium-editor-action-underline {
text-decoration: underline; }
.medium-editor-toolbar li .medium-editor-action-pre {
font-family: Consolas, "Liberation Mono", Menlo, Courier, monospace;
font-size: 12px;
font-weight: 100;
padding: 15px 0; }
.medium-editor-toolbar-active {
visibility: visible; }
.medium-editor-sticky-toolbar {
position: fixed;
top: 1px; }
.medium-editor-toolbar-active.medium-editor-stalker-toolbar {
-webkit-animation: medium-editor-pop-upwards 160ms forwards linear;
animation: medium-editor-pop-upwards 160ms forwards linear; }
.medium-editor-action-bold {
font-weight: bolder; }
.medium-editor-action-italic {
font-style: italic; }
.medium-editor-toolbar-form {
display: none; }
.medium-editor-toolbar-form input,
.medium-editor-toolbar-form a {
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; }
.medium-editor-toolbar-form .medium-editor-toolbar-form-row {
line-height: 14px;
margin-left: 5px;
padding-bottom: 5px; }
.medium-editor-toolbar-form .medium-editor-toolbar-input,
.medium-editor-toolbar-form label {
border: none;
box-sizing: border-box;
font-size: 14px;
margin: 0;
padding: 6px;
width: 316px;
display: inline-block; }
.medium-editor-toolbar-form .medium-editor-toolbar-input:focus,
.medium-editor-toolbar-form label:focus {
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
border: none;
box-shadow: none;
outline: 0; }
.medium-editor-toolbar-form a {
display: inline-block;
font-size: 24px;
font-weight: bolder;
margin: 0 10px;
text-decoration: none; }
.medium-editor-toolbar-actions:after {
clear: both;
content: "";
display: table; }
[data-medium-editor-element] img {
max-width: 100%; }
[data-medium-editor-element] sub {
vertical-align: sub; }
[data-medium-editor-element] sup {
vertical-align: super; }
.medium-editor-hidden {
display: none; }
.medium-toolbar-arrow-under:after {
border-color: rgba(19, 22, 24, 0.95) transparent transparent transparent;
top: 40px;
}
.medium-toolbar-arrow-over:before {
border-color: transparent transparent rgba(19, 22, 24, 0.95) transparent;
}
.medium-editor-toolbar {
background-color: rgba(19, 22, 24, 0.95);
border-radius: 4px;
}
.medium-editor-toolbar li button {
background-color: transparent;
border: none;
border-right: 1px solid rgba(0, 0, 0, 0.2);
box-sizing: border-box;
color: #efefef;
height: 40px;
min-width: 40px;
padding: 0;
-webkit-transition: background-color 0.2s ease-in, color 0.2s ease-in;
transition: background-color 0.2s ease-in, color 0.2s ease-in;
}
.medium-editor-toolbar li button:hover {
background-color: #12171a;
color: #fff;
}
.medium-editor-toolbar li .medium-editor-button-first {
border-bottom-left-radius: 4px;
border-top-left-radius: 4px;
}
.medium-editor-toolbar li .medium-editor-button-last {
border-bottom-right-radius: 4px;
border-right: none;
border-top-right-radius: 4px;
}
.medium-editor-toolbar li .medium-editor-button-active {
position: relative;
color: #fff;
}
.medium-editor-toolbar li .medium-editor-button-active {
&:after {
content: " ";
position: absolute;
left: 0; right: 0; bottom: 0;
height: 3px;
background-color: @primary;
}
}
.medium-editor-toolbar-form {
background: transparent;
border-radius: 4px;
color: #fff;
}
.medium-editor-toolbar-form .medium-editor-toolbar-input {
background: transparent;
color: #fff;
height: 40px;
}
.medium-editor-toolbar-form .medium-editor-toolbar-input::-webkit-input-placeholder {
color: #fff;
color: rgba(255, 255, 255, 0.8);
}
.medium-editor-toolbar-form .medium-editor-toolbar-input:-moz-placeholder {
/* Firefox 18- */
color: #fff;
color: rgba(255, 255, 255, 0.8);
}
.medium-editor-toolbar-form .medium-editor-toolbar-input::-moz-placeholder {
/* Firefox 19+ */
color: #fff;
color: rgba(255, 255, 255, 0.8);
}
.medium-editor-toolbar-form .medium-editor-toolbar-input:-ms-input-placeholder {
color: #fff;
color: rgba(255, 255, 255, 0.8);
}
.medium-editor-toolbar-form a {
color: #fff;
font-size: 14px;
}
.medium-editor-toolbar-anchor-preview {
background: rgba(19, 22, 24, 0.95);
border-radius: 4px;
color: #fff;
}
.medium-editor-placeholder:after {
color: rgba(19, 22, 24, 0.95);
}
}
================================================
FILE: lib/shared/components/portal.jsx
================================================
import Component from 'components/component';
import ReactDOM from 'react-dom';
import {PropTypes} from 'react';
const renderSubtreeIntoContainer = ReactDOM.unstable_renderSubtreeIntoContainer;
export default class Portal extends Component {
static propTypes = {
children: PropTypes.node.isRequired,
attachTo: PropTypes.string
};
componentDidMount () {
this._target =
this.props.attachTo ?
document.getElementById(this.props.attachTo).appendChild(document.createElement('div')) :
document.body.appendChild(document.createElement('div'));
this._portal = renderSubtreeIntoContainer(this, this.props.children, this._target);
}
componentDidUpdate () {
this._portal = renderSubtreeIntoContainer(this, this.props.children, this._target);
}
componentWillUnmount () {
ReactDOM.unmountComponentAtNode(this._target);
if (this.props.attachTo) {
document.getElementById(this.props.attachTo).removeChild(this._target);
} else {
document.body.removeChild(this._target);
}
}
render () {
return null;
}
}
================================================
FILE: lib/shared/decorators/bind.js
================================================
export default function bind (target, key, {value: fn}) {
return {
configurable: true,
get () {
const value = fn.bind(this);
Object.defineProperty(this, key, {
value,
configurable: true,
writable: true
});
return value;
}
};
}
================================================
FILE: lib/shared/decorators/debounce.js
================================================
export default function debounce (delay = 300) {
return (target, key, descriptor) => {
const fn = descriptor.value;
let args;
let context;
let timer;
descriptor.value = function debounced (...a) {
args = a;
context = this;
clearTimeout(timer);
timer = setTimeout(() => {
fn.apply(context, args);
args = context = timer = null;
}, delay);
};
return descriptor;
};
}
================================================
FILE: lib/shared/decorators/query-props.jsx
================================================
import forEach from 'lodash.foreach';
import hoistStatics from 'hoist-non-react-statics';
import qs from 'query-string';
import Component from 'components/component';
import React, {PropTypes} from 'react';
function sanatizeQueryObject (query) {
if (query.limit) {
query.limit = parseInt(query.limit, 10);
}
if (query.page) {
query.page = parseInt(query.page, 10);
}
return query;
}
export function getQueryVariables (query) {
const queryVariables = {};
if (query.sort) {
queryVariables.sort = {
value: query.sort,
type: 'String'
};
}
if (query.order) {
queryVariables.order = {
value: query.order,
type: 'String'
};
}
if (query.limit) {
queryVariables.limit = {
value: query.limit,
type: 'Int'
};
}
if (query.filters) {
queryVariables.filters = {
value: query.filters,
type: '[Filter]'
};
}
if (query.page) {
queryVariables.page = {
value: query.page,
type: 'Int'
};
}
if (query.search && query.s) {
queryVariables.search = {
value: query.search,
type: 'String'
};
queryVariables.s = {
value: query.s,
type: 'String'
};
}
return queryVariables;
}
const _defaultQuery = {
page: 1,
limit: 10
};
export default function queryProps (defaultQuery = _defaultQuery) {
return function wrapWithQueryProps (WrappedComponent) {
class QueryProps extends Component {
static propTypes = {
query: PropTypes.object,
location: PropTypes.object
};
getInitState (props = this.props) {
if (!props.location.query) {
props.location.query = qs.parse(props.location.search);
}
const query = sanatizeQueryObject(Object.assign(
{},
defaultQuery,
props.location.query
));
const queryVariables = getQueryVariables(query);
return {
queryVariables,
query,
hasQueryChanged: true
};
}
componentWillReceiveProps (nextProps) {
const nextState = {};
if (!nextProps.location.query) {
nextProps.location.query = qs.parse(nextProps.location.search);
}
const nextQuery = sanatizeQueryObject(Object.assign(
{},
defaultQuery,
nextProps.location.query
));
if (this.hasQueryChanged(nextQuery)) {
nextState.hasQueryChanged = true;
Object.assign(nextState, this.getInitState(nextProps));
} else {
nextState.hasQueryChanged = false;
}
this.setState(nextState);
}
static defaultQuery = defaultQuery;
hasQueryChanged (newQuery) {
const currentQuery = this.state.query;
let changed = !newQuery && currentQuery || newQuery && !currentQuery;
if (!changed && newQuery) {
forEach(newQuery, (value, key) => {
if (!currentQuery[key] || currentQuery[key] !== value) {
changed = true;
}
});
}
if (!changed && currentQuery) {
forEach(currentQuery, (value, key) => {
if (!newQuery[key] || newQuery[key] !== value) {
changed = true;
}
});
}
return changed;
}
render () {
return ;
}
}
return hoistStatics(QueryProps, WrappedComponent);
};
}
================================================
FILE: lib/shared/elements/button/classes.js
================================================
import jss from 'helpers/stylesheet';
export default jss.createRules({
holder: {
textAlign: 'center'
},
button: {
color: '#ffffff',
backgroundColor: '#282828',
borderRadius: '3px 3px 3px 3px',
padding: '11px 20px 11px 20px',
maxWidth: '250px',
display: 'inline-block',
cursor: 'pointer',
':hover': {
color: '#282828',
backgroundColor: '#ffffff'
}
},
sided: {
display: 'block',
'>div': {
display: 'table-cell',
verticalAlign: 'middle'
}
}
});
================================================
FILE: lib/shared/elements/button/index.jsx
================================================
import cx from 'classnames';
import forEach from 'lodash.foreach';
import React, {PropTypes} from 'react';
import {changeElementChildren} from 'actions/page-builder';
import classes from './classes';
import propsSchema from './props-schema';
import settings from './settings';
import style from './style';
import Component from '../component';
import Element from '../element';
export default class Button extends Component {
static propTypes = {
layout: PropTypes.string.isRequired,
arrange: PropTypes.string.isRequired,
styleClassMap: PropTypes.object,
children: PropTypes.node,
relax: PropTypes.object.isRequired
};
static defaultProps = {
layout: 'text',
arrange: 'side'
};
static defaultChildren = [
{
tag: 'TextBox',
children: 'Button text',
subComponent: true
}
];
static propsSchema = propsSchema;
static settings = settings;
static style = style;
componentWillReceiveProps (nextProps) {
const {relax} = this.props;
const editing = relax.editing;
if (editing && relax.selected) {
// Check if layout changed
if (nextProps.layout !== this.props.layout) {
// 'text', 'icontext', 'texticon', 'icon'
const newChildren = [];
let textChild = false;
let iconChild = false;
if (nextProps.layout === 'text' || nextProps.layout === 'texticon' || nextProps.layout === 'icontext') {
forEach(relax.element.children, (child) => {
if (child.tag === 'TextBox') {
textChild = child;
}
});
if (!textChild) {
textChild = {
tag: 'TextBox',
children: 'Button text',
subComponent: true
};
}
}
if (nextProps.layout === 'icon' || nextProps.layout === 'texticon' || nextProps.layout === 'icontext') {
forEach(relax.element.children, (child) => {
if (child.tag === 'Icon') {
iconChild = child;
}
});
if (!iconChild) {
iconChild = {
tag: 'Icon',
subComponent: true
};
}
}
if (iconChild && textChild) {
if (nextProps.layout === 'icon' || nextProps.layout === 'icontext') {
newChildren.push(iconChild);
if (nextProps.layout === 'icontext') {
newChildren.push(textChild);
}
} else if (nextProps.layout === 'text' || nextProps.layout === 'texticon') {
newChildren.push(textChild);
if (nextProps.layout === 'texticon') {
newChildren.push(iconChild);
}
}
} else {
newChildren.push(iconChild || textChild);
}
relax.dispatch(changeElementChildren(relax.element.id, newChildren));
}
}
}
render () {
const classMap = this.props.styleClassMap || {};
const props = {
htmlTag: 'div',
...this.props.relax,
settings,
className: cx(classes.holder, classMap.holder)
};
return (
{this.renderChildren()}
);
}
renderChildren () {
let result;
if (this.props.arrange === 'blocks' || this.props.layout === 'text' || this.props.layout === 'icon') {
result = this.props.children;
} else {
result = (
{this.props.children[0]}
{this.props.children[1]}
);
}
return result;
}
}
================================================
FILE: lib/shared/elements/button/props-schema.js
================================================
export default [
{
label: 'Layout',
type: 'Select',
id: 'layout',
props: {
labels: ['Text', 'Icon - Text', 'Text - Icon', 'Icon'],
values: ['text', 'icontext', 'texticon', 'icon']
},
unlocks: {
icontext: [
{
label: 'Arrange',
type: 'Select',
id: 'arrange',
props: {
labels: ['Side by side', 'Blocks'],
values: ['side', 'blocks']
}
}
],
texticon: [
{
label: 'Arrange',
type: 'Select',
id: 'arrange',
props: {
labels: ['Side by side', 'Blocks'],
values: ['side', 'blocks']
}
}
]
}
}
];
================================================
FILE: lib/shared/elements/button/settings.js
================================================
export default {
icon: {
class: 'nc-icon-mini ui-2_link-66'
},
category: 'content',
drop: false,
drag: {}
};
================================================
FILE: lib/shared/elements/button/style.js
================================================
import Utils from 'helpers/utils';
import {getColorString} from 'helpers/colors';
export default {
type: 'button',
options: [
{
label: 'Layout',
type: 'Section',
id: 'layoutSection',
unlocks: [
{
label: 'Size',
type: 'Select',
id: 'size',
props: {
labels: ['Full Width', 'Fit Content', 'Max Width (px)', 'Strict (px)'],
values: ['full', 'fit', 'max', 'strict']
},
unlocks: {
fit: [
{
label: 'Horizontal alignment',
type: 'Select',
id: 'alignment',
props: {
labels: ['Left', 'Center', 'Right'],
values: ['left', 'center', 'right']
}
}
],
max: [
{
label: 'Max Width',
type: 'Number',
id: 'maxWidth',
props: {
allowed: ['%', 'px']
}
},
{
label: 'Horizontal alignment',
type: 'Select',
id: 'alignment',
props: {
labels: ['Left', 'Center', 'Right'],
values: ['left', 'center', 'right']
}
}
],
strict: [
{
label: 'Width',
type: 'Pixels',
id: 'width'
},
{
label: 'Height',
type: 'Pixels',
id: 'height'
},
{
label: 'Horizontal alignment',
type: 'Select',
id: 'alignment',
props: {
labels: ['Left', 'Center', 'Right'],
values: ['left', 'center', 'right']
}
}
]
}
},
{
label: 'Padding',
type: 'Optional',
id: 'usePadding',
unlocks: [
{
type: 'Padding',
id: 'padding'
}
]
}
]
},
{
label: 'Default state',
type: 'Section',
id: 'defaultSection',
unlocks: [
{
label: 'Text Color',
type: 'Optional',
id: 'useColor',
unlocks: [
{
type: 'Color',
id: 'color'
}
]
},
{
label: 'Background',
type: 'Optional',
id: 'useBackground',
unlocks: [
{
type: 'Color',
id: 'backgroundColor'
}
]
},
{
label: 'Border',
type: 'Optional',
id: 'useBorder',
unlocks: [
{
type: 'Border',
id: 'border'
}
]
},
{
label: 'Rounded Corners',
type: 'Optional',
id: 'useCorners',
unlocks: [
{
type: 'Corners',
id: 'corners'
}
]
}
]
},
{
label: 'Overed state',
type: 'Section',
id: 'overedSection',
unlocks: [
{
label: 'Text Color',
type: 'Optional',
id: 'useColorOver',
unlocks: [
{
type: 'Color',
id: 'colorOver'
}
]
},
{
label: 'Background',
type: 'Optional',
id: 'useBackgroundOver',
unlocks: [
{
type: 'Color',
id: 'backgroundColorOver'
}
]
},
{
label: 'Border Color',
type: 'Optional',
id: 'useBorderColorOver',
unlocks: [
{
type: 'Color',
id: 'borderColorOver'
}
]
},
{
label: 'Rounded Corners',
type: 'Optional',
id: 'useCornersOver',
unlocks: [
{
type: 'Corners',
id: 'cornersOver'
}
]
},
{
label: 'Animation on Over',
type: 'Optional',
id: 'useAnimation',
unlocks: [
{
type: 'Number',
id: 'animationDuration',
props: {
label: 'ms'
}
}
]
}
]
},
{
label: 'Pressed state',
type: 'Section',
id: 'pressedSection',
unlocks: [
{
label: 'Text Color',
type: 'Optional',
id: 'useColorPressed',
unlocks: [
{
type: 'Color',
id: 'colorPressed'
}
]
},
{
label: 'Background',
type: 'Optional',
id: 'useBackgroundPressed',
unlocks: [
{
type: 'Color',
id: 'backgroundColorPressed'
}
]
},
{
label: 'Border Color',
type: 'Optional',
id: 'useBorderColorPressed',
unlocks: [
{
type: 'Color',
id: 'borderColorPressed'
}
]
}
]
}
],
defaults: {
size: 'fit',
maxWidth: '100%',
width: '70px',
height: '70px',
alignment: 'center',
usePadding: false,
padding: '0px',
useColor: false,
color: {
value: '#ffffff',
opacity: 100
},
useBackground: false,
backgroundColor: {
value: '#ffffff',
opacity: 100
},
useBorder: false,
border: false,
useCorners: false,
corners: '0px',
useColorOver: false,
colorOver: {
value: '#ffffff',
opacity: 100
},
useBackgroundOver: false,
backgroundColorOver: {
value: '#ffffff',
opacity: 100
},
useBorderColorOver: false,
borderColorOver: {
value: '#ffffff',
opacity: 100
},
useCornersOver: false,
cornersOver: '0px',
useAnimation: false,
animationDuration: 500,
useColorPressed: false,
colorPressed: {
value: '#ffffff',
opacity: 100
},
useBackgroundPressed: false,
backgroundColorPressed: {
value: '#ffffff',
opacity: 100
},
useBorderColorPressed: false,
borderColorPressed: {
value: '#ffffff',
opacity: 100
}
},
rules: (props) => {
const rules = {
button: {
backgroundColor: props.useBackground && getColorString(props.backgroundColor),
borderRadius: props.useCorners && props.corners,
padding: props.usePadding && props.padding,
'*': {
color: props.useColor && getColorString(props.color)
},
':hover': {
'*': {
color: props.useColorOver && getColorString(props.colorOver)
},
borderRadius: props.useCornersOver && props.cornersOver,
backgroundColor: props.useBackgroundOver && getColorString(props.backgroundColorOver)
},
':active': {
'*': {
color: props.useColorPressed && getColorString(props.colorPressed)
},
backgroundColor: props.useBackgroundPressed && getColorString(props.backgroundColorPressed)
}
},
holder: {}
};
if (props.size === 'strict') {
rules.button.width = props.width;
rules.button.height = props.height;
} else if (props.size === 'fit') {
rules.button.display = 'inline-block';
} else if (props.size === 'max') {
rules.button.maxWidth = props.maxWidth;
}
if (props.size !== 'full') {
rules.holder.textAlign = props.alignment;
rules.button.display = 'inline-block';
}
if (props.useAnimation) {
rules.button.transition = `all ${props.animationDuration}ms cubic-bezier(0.190, 1.000, 0.220, 1.000)`;
}
if (props.useBorder) {
Utils.applyBorders(rules.button, props.border);
if (props.useBorderColorOver) {
rules.button[':hover'].borderColor = getColorString(props.borderColorOver);
}
if (props.useBorderColorPressed) {
rules.button[':active'].borderColor = getColorString(props.borderColorPressed);
}
}
return rules;
}
};
================================================
FILE: lib/shared/elements/column/index.jsx
================================================
import React, {PropTypes} from 'react';
import propsSchema from './props-schema';
import settings from './settings';
import Component from '../component';
import Element from '../element';
export default class Column extends Component {
static propTypes = {
padding: PropTypes.string.isRequired,
vertical: PropTypes.string.isRequired,
left: PropTypes.number,
right: PropTypes.number,
bottom: PropTypes.number,
layout: PropTypes.object,
relax: PropTypes.object.isRequired
};
static defaultProps = {
padding: '15px',
vertical: 'top'
};
static propsSchema = propsSchema;
static settings = settings;
render () {
const layout = this.props.layout || {
width: 'auto'
};
const style = {
display: layout.width === 'block' ? 'block' : 'table-cell',
verticalAlign: this.props.vertical
};
if (this.props.left || this.props.right) {
style.padding = `0px ${this.props.right}px 0px ${this.props.left}px`;
}
if (this.props.bottom) {
style.marginBottom = this.props.bottom;
}
const contentStyle = {
padding: this.props.padding
};
if (layout.width !== 'block') {
style.width = `${layout.widthPerc}`;
}
return (
{this.renderContent()}
);
}
}
================================================
FILE: lib/shared/elements/column/props-schema.js
================================================
export default [
{
label: 'Padding',
type: 'Padding',
id: 'padding'
},
{
label: 'Content vertical align',
type: 'Select',
id: 'vertical',
props: {
labels: ['Top', 'Center', 'Bottom'],
values: ['top', 'middle', 'bottom']
}
}
];
================================================
FILE: lib/shared/elements/column/settings.js
================================================
export default {
icon: {
class: 'nc-icon-mini design_distribute-horizontal'
},
category: 'structure',
drop: {
rejects: 'Section',
customDropArea: true
},
drag: {
droppableOn: 'Columns'
}
};
================================================
FILE: lib/shared/elements/columns/classes.js
================================================
import jss from 'helpers/stylesheet';
export default jss.createRules({
row: {
display: 'table',
tableLayout: 'fixed',
width: '100%'
}
});
================================================
FILE: lib/shared/elements/columns/index.jsx
================================================
import utils from 'helpers/utils';
import Droppable from 'components/dnd/droppable';
import React, {PropTypes} from 'react';
import classes from './classes';
import propsSchema from './props-schema';
import settings from './settings';
import Component from '../component';
import Element from '../element';
export default class Columns extends Component {
static propTypes = {
spacing: PropTypes.string,
spacingRows: PropTypes.string,
columnsDisplay: PropTypes.array.isRequired,
children: PropTypes.node,
relax: PropTypes.object.isRequired
};
static defaultProps = {
spacing: '10px',
spacingRows: '10px',
columnsDisplay: []
};
static defaultChildren = [
{tag: 'Column'}, {tag: 'Column'}
];
static propsSchema = propsSchema;
static settings = settings;
render () {
return (
{this.renderChildren()}
);
}
renderChildren () {
const {columnsDisplay, relax, spacing} = this.props;
const children = [];
const numChildren = this.props.children && this.props.children.length || 0;
const layout = utils.parseColumnsDisplay(columnsDisplay, numChildren, relax.display !== 'desktop');
const editing = relax.editing;
const spacingNum = parseFloat(spacing, 10);
const spaceThird = Math.round(spacingNum / 3 * 100) / 100;
const spaceSides = spaceThird * 2;
let result;
const dropInfo = {
id: relax.element.id
};
if (numChildren > 0) {
for (let i = 0; i < numChildren; i++) {
if (layout[i].width === 'block') {
children.push(this.renderBlock(
this.props.children[i],
layout[i],
i !== numChildren - 1 ? spacingNum : 0
));
} else {
const columns = [];
for (i; i < numChildren; i++) {
if (layout[i].width !== 'block' && !(columns.length > 0 && layout[i].break)) {
const isLastColumn = (
columns.length !== 0 &&
(i === numChildren - 1 || (layout[i + 1].width === 'block' || layout[i + 1].break))
);
let left;
let right;
if (columns.length === 0) {
left = 0;
right = spaceSides;
} else if (isLastColumn) {
left = spaceSides;
right = 0;
} else {
left = spaceThird;
right = spaceThird;
}
columns.push(this.renderColumn(this.props.children[i], layout[i], left, right));
} else {
i--;
break;
}
}
if (editing && relax.display === 'desktop') {
result = (
{columns}
);
break;
} else {
const style = {};
if (i < numChildren - 1) {
style.paddingBottom = this.props.spacingRows;
}
children.push(
{columns}
);
}
}
}
} else if (editing) {
result = (
);
}
return result || children;
}
renderColumn (child, layout, left, right) {
return React.cloneElement(child, {
layout,
left,
right
});
}
renderBlock (child, layout, bottom) {
return React.cloneElement(child, {
layout,
bottom
});
}
}
================================================
FILE: lib/shared/elements/columns/props-schema.js
================================================
export default [
{
id: 'add-button',
label: false,
type: 'Button',
props: {
label: 'Add column',
action: 'addElement',
actionProps: {tag: 'Column'}
}
},
{
label: 'Space between columns',
type: 'Pixels',
id: 'spacing'
},
{
label: 'Space between rows (not used on desktop)',
type: 'Pixels',
id: 'spacingRows'
},
{
label: 'Columns display',
type: 'ManageColumns',
id: 'columnsDisplay'
}
];
================================================
FILE: lib/shared/elements/columns/settings.js
================================================
export default {
icon: {
class: 'nc-icon-mini '
},
category: 'structure',
drop: {
orientation: 'horizontal',
selectionChildren: 'column',
accepts: 'Column',
customDropArea: true
},
drag: {}
};
================================================
FILE: lib/shared/elements/component.jsx
================================================
import Component from 'components/component';
import Droppable from 'components/dnd/droppable';
import React, {PropTypes} from 'react';
import Empty from './element/empty';
export default class ElementComponent extends Component {
static propTypes = {
children: PropTypes.node,
relax: PropTypes.object.isRequired
};
renderContent (customProps, children = this.props.children) {
const {relax} = this.props;
const editing = relax.editing;
let result;
if (editing) {
const droppableProps = Object.assign({
dropInfo: {
id: relax.element.id
},
type: relax.element.tag,
placeholder: true,
placeholderRender: ::this.renderPlaceholder
}, this.constructor.settings.drop);
result = (
{children}
);
} else {
result = children;
}
return result;
}
renderPlaceholder (options) {
const {relax} = this.props;
return (
);
}
}
================================================
FILE: lib/shared/elements/container/index.jsx
================================================
import React, {PropTypes} from 'react';
import propsSchema from './props-schema';
import settings from './settings';
import style from './style';
import Component from '../component';
import Element from '../element';
export default class Container extends Component {
static propTypes = {
styleClassMap: PropTypes.object,
children: PropTypes.node,
relax: PropTypes.object.isRequired
};
static propsSchema = propsSchema;
static settings = settings;
static style = style;
render () {
const classMap = this.props.styleClassMap || {};
const props = {
...this.props.relax,
htmlTag: 'div',
style: {
position: 'relative'
},
className: classMap.container,
settings
};
return (
{this.props.children}
);
}
}
================================================
FILE: lib/shared/elements/container/props-schema.js
================================================
export default [];
================================================
FILE: lib/shared/elements/container/settings.js
================================================
export default {
icon: {
class: 'nc-icon-mini ui-2_enlarge-58'
},
category: 'structure',
drop: {
rejects: 'Section'
},
drag: {}
};
================================================
FILE: lib/shared/elements/container/style.js
================================================
import utils from 'helpers/utils';
import {getColorString, applyBackground} from 'helpers/colors';
export default {
type: 'container',
options: [
{
label: 'Background Color',
type: 'Optional',
id: 'useBackgroundColor',
unlocks: [
{
type: 'Color',
id: 'backgroundColor',
props: {
gradients: true
}
}
]
},
{
label: 'Max Width',
type: 'Optional',
id: 'useMaxWidth',
unlocks: [
{
label: 'Maximum Width',
type: 'Pixels',
id: 'widthPx',
props: {
min: 0,
max: false
}
},
{
label: 'Content horizontal alignment',
type: 'Select',
id: 'contentHorizontal',
props: {
labels: ['Left', 'Center', 'Right'],
values: ['left', 'center', 'right']
}
}
]
},
{
label: 'Padding',
type: 'Padding',
id: 'padding'
},
{
label: 'Rounded Corners',
type: 'Optional',
id: 'useCorners',
unlocks: [
{
type: 'Corners',
id: 'corners'
}
]
},
{
label: 'Border',
type: 'Optional',
id: 'useBorder',
unlocks: [
{
type: 'Border',
id: 'border'
}
]
},
{
label: 'Shadow',
type: 'BoxShadow',
id: 'shadow'
}
],
defaults: {
useBackgroundColor: false,
backgroundColor: {
value: '#ffffff',
opacity: 100
},
useMaxWidth: true,
widthPx: '1000px',
contentHorizontal: 'center',
padding: '20px',
useCorners: false,
corners: '0px',
useBorder: false,
shadow: []
},
rules: (props) => {
const rule = {};
const holderRule = {};
props.useBackgroundColor && applyBackground(rule, props.backgroundColor);
if (props.useMaxWidth) {
rule.maxWidth = props.widthPx;
rule.width = '100%';
rule.display = 'inline-block';
holderRule.textAlign = props.contentHorizontal;
}
rule.padding = props.padding;
rule.borderRadius = props.useCorners && props.corners;
if (props.useBorder) {
utils.applyBorders(rule, props.border);
}
if (props.shadow && props.shadow.length > 0) {
utils.applyBoxShadows(rule, props.shadow);
}
return {
container: rule,
holder: holderRule
};
},
getIdentifierLabel: (props) => {
let str = '';
if (props.useMaxWidth) {
str += props.widthPx;
} else {
str += 'Full';
}
str += ' | ';
if (props.useBackgroundColor) {
str += getColorString(props.backgroundColor);
} else {
str += 'transparent';
}
return str;
}
};
================================================
FILE: lib/shared/elements/counter/index.jsx
================================================
import cx from 'classnames';
import ReactCounter from 'react-counter';
import React, {PropTypes} from 'react';
import propsSchema from './props-schema';
import settings from './settings';
import Component from '../component';
import Element from '../element';
export default class Counter extends Component {
static propTypes = {
icon: PropTypes.string.isRequired,
style: PropTypes.any.isRequired,
styleClassMap: PropTypes.object,
align: PropTypes.string.isRequired,
begin: PropTypes.number.isRequired,
end: PropTypes.number.isRequired,
duration: PropTypes.number.isRequired,
relax: PropTypes.object.isRequired
};
static defaultProps = {
begin: 0,
end: 100,
duration: 2000,
align: 'center'
};
static propsSchema = propsSchema;
static settings = settings;
static style = 'text';
getInitState () {
return {
animate: false
};
}
onEnterScreen () {
this.setState({
animate: true
});
}
render () {
const classMap = this.props.styleClassMap || {};
const props = {
...this.props.relax,
htmlTag: 'div',
settings,
onEnterScreen: this.onEnterScreen.bind(this),
className: cx(classMap.text),
style: {
textAlign: this.props.align
}
};
return (
{this.renderCounter()}
);
}
renderCounter () {
let result;
if (this.state.animate) {
result = (
);
} else {
result = {this.props.begin};
}
return result;
}
}
================================================
FILE: lib/shared/elements/counter/props-schema.js
================================================
export default [
{
type: 'Columns',
options: [
{
label: 'Begin',
type: 'Number',
id: 'begin'
},
{
label: 'End',
type: 'Number',
id: 'end'
}
]
},
{
type: 'Columns',
options: [
{
label: 'Duration',
type: 'Number',
id: 'duration'
},
{
label: 'Alignment',
type: 'Select',
id: 'align',
props: {
labels: ['Left', 'Center', 'Right'],
values: ['left', 'center', 'right']
}
}
]
}
];
================================================
FILE: lib/shared/elements/counter/settings.js
================================================
export default {
icon: {
class: 'nc-icon-mini ui-2_time-countdown'
},
category: 'content',
style: 'text',
drop: false,
drag: {}
};
================================================
FILE: lib/shared/elements/date/index.jsx
================================================
import cx from 'classnames';
import moment from 'moment';
import React, {PropTypes} from 'react';
import propsSchema from './props-schema';
import settings from './settings';
import Component from '../component';
import Element from '../element';
export default class Counter extends Component {
static propTypes = {
styleClassMap: PropTypes.object.isRequired,
date: PropTypes.number.isRequired,
format: PropTypes.string.isRequired,
customFormat: PropTypes.string,
relax: PropTypes.object.isRequired
};
static defaultProps = {
date: Date.now(),
format: 'LL',
customFormat: 'MMMM Do YYYY, h:mm:ss a'
};
static propsSchema = propsSchema;
static settings = settings;
static style = 'text';
render () {
const classMap = this.props.styleClassMap || {};
const props = {
...this.props.relax,
htmlTag: 'div',
settings,
className: cx(classMap.text)
};
const format = this.props.format;
const date = moment(parseInt(this.props.date, 10));
let dateStr = '';
if (format === 'fromNow') {
dateStr = date.fromNow();
} else if (format === 'custom') {
dateStr = date.format(this.props.customFormat);
} else {
dateStr = date.format(format);
}
return (
{dateStr}
);
}
}
================================================
FILE: lib/shared/elements/date/props-schema.jsx
================================================
import forEach from 'lodash.foreach';
import moment from 'moment';
const momentDate = moment();
const dateFormats = [
'fromNow',
'L',
'l',
'LL',
'll',
'LLL',
'lll',
'LLLL',
'llll',
'custom'
];
const labels = [];
forEach(dateFormats, (format) => {
if (format === 'fromNow') {
labels.push(momentDate.fromNow());
} else if (format === 'custom') {
labels.push('Custom');
} else {
labels.push(momentDate.format(format));
}
});
export default [
{
label: 'Date',
type: 'Date',
id: 'date'
},
{
label: 'Format',
type: 'Select',
id: 'format',
props: {
values: dateFormats,
labels
},
unlocks: {
custom: [
{
label: 'Custom Format',
type: 'String',
id: 'customFormat'
}
]
}
}
];
================================================
FILE: lib/shared/elements/date/settings.js
================================================
export default {
icon: {
class: 'nc-icon-mini ui-1_calendar-60'
},
category: 'content',
drop: false,
drag: {}
};
================================================
FILE: lib/shared/elements/dynamic-list/classes.js
================================================
import jss from 'helpers/stylesheet';
export default jss.createRules({
row: {
display: 'table',
width: '100%',
tableLayout: 'fixed',
textAlign: 'left'
},
column: {
display: 'table-cell',
verticalAlign: 'top',
position: 'relative'
}
});
================================================
FILE: lib/shared/elements/dynamic-list/container.jsx
================================================
import * as elementsActions from 'actions/elements';
import utils from 'helpers/utils';
import React, {PropTypes} from 'react';
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
import {buildQueryAndVariables} from 'relax-fragments';
import settings from './settings';
import Component from '../component';
import Element from '../element';
import List from './list';
@connect(
(state) => ({
elements: state.elements,
linkingData: state.pageBuilder.linkingData,
linkingDataElementId: state.pageBuilder.linkingDataElementId
}),
(dispatch) => bindActionCreators(elementsActions, dispatch)
)
export default class DynamicListContainer extends Component {
static fragments = {
schemaList: {
_id: 1,
title: 1,
date: 1,
publishedDate: 1,
updatedDate: 1,
slug: 1,
state: 1,
properties: 1
}
};
static propTypes = {
children: PropTypes.node,
schemaId: PropTypes.string,
dataLinking: PropTypes.object,
limit: PropTypes.number,
columns: PropTypes.number,
renderChildren: PropTypes.func.isRequired,
verticalGutter: PropTypes.number.isRequired,
horizontalGutter: PropTypes.number.isRequired,
getElementData: PropTypes.func.isRequired,
elements: PropTypes.object.isRequired,
schemaLinks: PropTypes.object,
relax: PropTypes.object.isRequired
};
getInitState () {
this.fetchData(this.props);
return {};
}
componentWillReceiveProps (nextProps) {
if (nextProps.schemaId !== this.props.schemaId ||
nextProps.dataLinking !== this.props.dataLinking ||
nextProps.limit > this.props.limit) {
this.fetchData(nextProps);
}
}
fetchData (props) {
if (props.schemaId) {
props.getElementData(props.elementId, buildQueryAndVariables(
this.constructor.fragments,
{
schemaList: {
schemaId: {
value: props.schemaId,
type: 'ID!'
},
limit: {
value: props.limit,
type: 'Int'
}
}
}
));
}
}
render () {
const props = {
htmlTag: 'div',
...this.props.relax,
settings
};
const {elements, relax} = this.props;
const entries = elements[relax.element.id] && elements[relax.element.id].schemaList || [];
const elementsLinks = utils.getElementsSchemaLinks(this.props.schemaLinks);
return (
{this.props.children}
);
}
}
================================================
FILE: lib/shared/elements/dynamic-list/index.jsx
================================================
import React from 'react';
import propsSchema from './props-schema';
import settings from './settings';
import Component from '../component';
import Container from './container';
export default class DynamicList extends Component {
static defaultProps = {
limit: 10,
columns: 2,
verticalGutter: '10px',
horizontalGutter: '10px'
};
static propsSchema = propsSchema;
static settings = settings;
render () {
return ;
}
}
================================================
FILE: lib/shared/elements/dynamic-list/list.jsx
================================================
import cx from 'classnames';
import Component from 'components/component';
import Droppable from 'components/dnd/droppable';
import React, {PropTypes} from 'react';
import classes from './classes';
import settings from './settings';
export default class List extends Component {
static propTypes = {
children: PropTypes.node,
entries: PropTypes.array.isRequired,
limit: PropTypes.number,
columns: PropTypes.number,
renderChildren: PropTypes.func.isRequired,
verticalGutter: PropTypes.number.isRequired,
horizontalGutter: PropTypes.number.isRequired,
elementsLinks: PropTypes.object.isRequired,
linkingData: PropTypes.bool.isRequired,
linkingDataElementId: PropTypes.string.isRequired,
relax: PropTypes.object.isRequired
};
isLinkingData () {
const {relax, linkingData, linkingDataElementId} = this.props;
return linkingData && linkingDataElementId === relax.element.id;
}
render () {
const items = [];
let number = Math.min(this.props.entries.length, this.props.limit);
if (number === 0) {
number = this.props.limit;
}
for (let i = 0; i < number; i) {
if (this.props.columns > 1) {
const columnItems = [];
for (let a = 0; a < this.props.columns && i < number; a++) {
columnItems.push(this.renderItem(i, a === 0, a === this.props.columns - 1));
i++;
}
if (columnItems.length < this.props.columns) {
const missing = this.props.columns - columnItems.length;
for (let c = 0; c < missing; c++) {
columnItems.push(this.renderItem(i, false, c === missing - 1, true));
}
}
items.push(this.renderRow(columnItems, i >= number));
} else {
items.push(this.renderItem(i));
i++;
}
}
return (
{items}
);
}
renderRow (items, isLast) {
const style = {};
if (!isLast) {
if (this.isLinkingData()) {
style.borderBottom = `${this.props.verticalGutter} solid rgba(0, 0, 0, 0.8)`;
} else {
style.marginBottom = this.props.verticalGutter;
}
}
return (
{items}
);
}
renderItem (key, isFirst, isLast, dummy = false) {
let result;
const {relax, entries, elementsLinks, children, horizontalGutter, columns} = this.props;
const editing = relax.editing;
const schemaEntry = entries && entries[key];
const content = children && relax.renderChildren(relax.element.children, {
elementsLinks,
schemaEntry
});
const spaceThird = Math.round(parseInt(horizontalGutter, 10) / 3 * 100) / 100;
const spaceSides = spaceThird * 2;
if (!dummy) {
if (editing) {
result = (
{content}
);
} else {
result = content;
}
}
const style = {};
if (columns > 1) {
style.width = `${100 / columns}%`;
const isLinkingData = this.isLinkingData();
const property = !dummy && isLinkingData && 'border' || 'padding';
if (isFirst) {
style[`${property}Right`] = cx(`${spaceSides}px`, property === 'border' && 'solid rgba(0, 0, 0, 0.8)');
} else if (isLast) {
style[`${property}Left`] = cx(`${spaceSides}px`, property === 'border' && 'solid rgba(0, 0, 0, 0.8)');
} else {
style[`${property}Right`] = cx(`${spaceThird}px`, property === 'border' && 'solid rgba(0, 0, 0, 0.8)');
style[`${property}Left`] = cx(`${spaceThird}px`, property === 'border' && 'solid rgba(0, 0, 0, 0.8)');
}
if (dummy && isLinkingData) {
style.backgroundColor = 'rgba(0, 0, 0, 0.8)';
}
}
return (
1 && classes.column)} style={style}>
{result}
);
}
}
================================================
FILE: lib/shared/elements/dynamic-list/props-schema.js
================================================
import React from 'react';
export default [
{
label: 'Schema assigned',
id: 'schemaId',
type: 'SchemaPicker'
},
{
label: false,
id: 'linkDataButton',
type: 'Button',
props: {
label: (
radio_button_checked
Link data
),
action: 'linkData'
}
},
{
label: 'Limit',
id: 'limit',
type: 'Number'
},
{
label: 'Columns',
id: 'columns',
type: 'Number'
},
{
label: 'Vertical gutter',
id: 'verticalGutter',
type: 'Pixels'
},
{
label: 'Horizontal gutter',
id: 'horizontalGutter',
type: 'Pixels'
},
{
label: 'Filter',
id: 'filters',
type: 'Filters'
},
{
label: 'Sort',
id: 'sorts',
type: 'Sorts'
}
];
================================================
FILE: lib/shared/elements/dynamic-list/settings.js
================================================
export default {
icon: {
class: 'nc-icon-mini ui-1_database'
},
drop: false,
drag: {}
};
================================================
FILE: lib/shared/elements/element/context-menu/context-menu.jsx
================================================
import Component from 'components/component';
import React, {PropTypes} from 'react';
import styles from './context-menu.less';
export default class ContextMenu extends Component {
static propTypes = {
element: PropTypes.object.isRequired,
opened: PropTypes.bool.isRequired,
open: PropTypes.func.isRequired,
close: PropTypes.func.isRequired,
addingSymbol: PropTypes.bool.isRequired,
symbolTitle: PropTypes.string.isRequired,
openAddingSymbol: PropTypes.func.isRequired,
closeAddingSymbol: PropTypes.func.isRequired,
onSymbolChange: PropTypes.func.isRequired,
makeElementSymbol: PropTypes.func.isRequired,
makeElementDynamic: PropTypes.func.isRequired,
duplicateElement: PropTypes.func.isRequired,
removeElement: PropTypes.func.isRequired
};
saveSymbol (event) {
event.preventDefault();
const {symbolTitle, makeElementSymbol, element} = this.props;
if (this.props.symbolTitle) {
makeElementSymbol(element.id, symbolTitle);
} else {
this.refs.titleInput.focus();
}
}
makeDynamic (event) {
event.preventDefault();
const {makeElementDynamic, element} = this.props;
makeElementDynamic(element.id);
}
duplicate (event) {
event.preventDefault();
const {duplicateElement, element} = this.props;
duplicateElement(element.id);
}
remove (event) {
event.preventDefault();
const {removeElement, element} = this.props;
removeElement(element.id);
}
render () {
const {opened} = this.props;
return opened ? this.renderOpened() : this.renderClosed();
}
renderOpened () {
const {addingSymbol} = this.props;
return addingSymbol ? this.renderAddingSymbol() : this.renderActions();
}
renderClosed () {
return (
);
}
renderAddingSymbol () {
const {closeAddingSymbol, symbolTitle, onSymbolChange} = this.props;
return (
);
}
renderActions () {
const {close, element, openAddingSymbol} = this.props;
return (
{element.label || element.tag}
Add to symbol library
Make dynamic
Duplicate
Remove
);
}
}
================================================
FILE: lib/shared/elements/element/context-menu/context-menu.less
================================================
@import '~styles/colors.less';
.closed {
width: 23px;
height: 23px;
border: 1px solid @primary;
display: inline-block;
position: absolute;
top: 4px;
right: 4px;
border-radius: 50%;
font-size: 11px;
line-height: 23px;
text-align: center;
color: @primary;
cursor: pointer;
pointer-events: all;
background-color: transparent;
}
.opened {
display: inline-block;
position: absolute;
top: 4px;
right: 4px;
pointer-events: all;
overflow: hidden;
padding-bottom: 7px;
width: 192px;
border-radius: 4px;
border: 1px solid @primary;
background-color: #ffffff;
-webkit-box-shadow: 0px 5px 9px 0px rgba(0,0,0,0.17);
-moz-box-shadow: 0px 5px 9px 0px rgba(0,0,0,0.17);
box-shadow: 0px 5px 9px 0px rgba(0,0,0,0.17);
text-align: center;
}
.label {
font-size: 10px;
line-height: 25px;
color: @primary;
padding-bottom: 3px;
text-transform: uppercase;
position: relative;
display: block;
text-align: center;
width: 100%;
:global i {
font-size: 14px;
position: absolute;
top: 0; left: 0;
line-height: 25px;
width: 25px;
text-align: center;
}
}
.form {
margin: 0;
}
.input {
margin: 0; padding: 0; outline: 0;
border: 1px solid rgba(0, 0, 0, 0.20);
height: 28px;
line-height: 28px;
color: #999999;
font-size: 12px;
font-weight: 300;
padding: 0 7px;
border-radius: 4px;
margin-top: 7px;
width: 175px;
&::-webkit-input-placeholder {
color: #dbdbdb;
}
&:-moz-placeholder {
color: #dbdbdb;
}
&::-moz-placeholder {
color: #dbdbdb;
}
&:-ms-input-placeholder {
color: #dbdbdb;
}
&::-ms-input-placeholder {
color: #dbdbdb;
}
&:placeholder-shown {
color: #dbdbdb;
}
&:focus {
border-color: @primary;
}
}
.saveButton {
color: @primary;
font-size: 12px;
font-weight: 800;
margin-top: 7px;
line-height: 25px;
text-transform: uppercase;
cursor: pointer;
}
.action {
line-height: 26px;
font-size: 12px;
color: #999999;
cursor: pointer;
text-transform: uppercase;
&:hover {
background-color: #efefef;
color: #333333;
}
}
================================================
FILE: lib/shared/elements/element/context-menu/index.js
================================================
import Component from 'components/component';
import React, {PropTypes} from 'react';
import {makeElementSymbol, makeElementDynamic, duplicateElement, removeElement} from 'actions/page-builder';
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
import ContextMenu from './context-menu';
@connect(
() => ({}),
(dispatch) => bindActionCreators({
makeElementSymbol,
makeElementDynamic,
duplicateElement,
removeElement
}, dispatch)
)
export default class ContextMenuContainer extends Component {
static propTypes = {
element: PropTypes.object.isRequired
};
getInitState () {
return {
opened: false,
addingSymbol: false,
symbolTitle: ''
};
}
openAddingSymbol () {
this.setState({
addingSymbol: true
});
}
closeAddingSymbol () {
this.setState({
addingSymbol: false
});
}
onSymbolChange (event) {
this.setState({
symbolTitle: event.target.value
});
}
open () {
this.setState({
opened: true
});
}
close () {
this.setState({
opened: false
});
}
render () {
return (
);
}
}
================================================
FILE: lib/shared/elements/element/element.jsx
================================================
import getElementPosition from 'helpers/get-element-position';
import utils from 'helpers/utils';
import velocity from 'velocity-animate';
import Component from 'components/component';
import Draggable from 'components/dnd/draggable';
import Droppable from 'components/dnd/droppable';
import React, {PropTypes} from 'react';
import {findDOMNode} from 'react-dom';
import Empty from './empty';
import Highlight from './highlight';
export default class Element extends Component {
static propTypes = {
display: PropTypes.string.isRequired,
editing: PropTypes.bool.isRequired,
settings: PropTypes.object.isRequired,
element: PropTypes.object.isRequired,
positionInParent: PropTypes.number.isRequired,
selected: PropTypes.bool.isRequired,
overed: PropTypes.bool.isRequired,
selectElement: PropTypes.func.isRequired,
htmlTag: PropTypes.string.isRequired,
className: PropTypes.string,
style: PropTypes.object,
animation: PropTypes.bool.isRequired,
animated: PropTypes.bool.isRequired,
animatedEditing: PropTypes.bool.isRequired,
children: PropTypes.node,
dragging: PropTypes.bool.isRequired,
overElement: PropTypes.func.isRequired,
outElement: PropTypes.func.isRequired,
onEnterScreen: PropTypes.func.isRequired,
startAnimation: PropTypes.func.isRequired,
resetAnimation: PropTypes.func.isRequired
};
static defaultProps = {
style: {},
className: ''
};
componentDidMount () {
const {editing, animation, onEnterScreen} = this.props;
if ((!editing && animation) || onEnterScreen) {
this.onScrollBind = ::this.onScroll;
window.addEventListener('scroll', this.onScrollBind);
this.onScroll();
}
if (editing) {
this.animationEditingBind = ::this.animationEditing;
window.addEventListener('animateElements', this.animationEditingBind);
}
}
componentWillUnmount () {
if (this.onScrollBind) {
window.removeEventListener('scroll', this.onScrollBind);
}
if (this.animationEditingBind) {
window.removeEventListener('animateElements', this.animationEditingBind);
}
if (this.animationTimeout) {
clearTimeout(this.animationTimeout);
}
}
animate () {
const dom = findDOMNode(this);
const {animation, startAnimation} = this.props;
startAnimation();
velocity(dom, animation.effect, {
duration: animation.duration,
display: null
});
}
animationInit () {
const animation = this.props.animation;
if (animation) {
this.animationTimeout = setTimeout(::this.animate, animation.delay);
}
}
animationEditing () {
if (this.props.animation) {
this.props.resetAnimation();
this.animationInit();
}
}
onScroll () {
const dom = findDOMNode(this);
const rect = dom.getBoundingClientRect();
if ((rect.top <= 0 && rect.bottom >= 0) || (rect.top > 0 && rect.top < window.outerHeight)) {
if (this.state.animation) {
this.animationInit();
}
if (this.props.onEnterScreen) {
this.props.onEnterScreen();
}
window.removeEventListener('scroll', this.onScrollBind);
}
}
onElementClick (event) {
const {selectElement, element} = this.props;
event.stopPropagation();
selectElement(element.id);
}
processAnimationStyle (style) {
const {editing, animation, animated, animatedEditing} = this.props;
if ((editing && animatedEditing) || (!editing && animation && !animated)) {
style.opacity = 0;
}
}
processPosition (style) {
const {element, display, editing} = this.props;
Object.assign(style, getElementPosition(element, display));
if (editing) {
if (style.position === 'fixed') {
if (style.top !== 'auto') {
if (utils.isPercentage(style.top)) {
const value = (1 - parseInt(style.top, 10) / 100) * 45;
style.top = `calc(${style.top} + ${value}px)`;
} else {
style.top = `calc(${style.top} + 45px)`;
}
}
if (style.bottom !== 'auto' && utils.isPercentage(style.bottom)) {
const value = parseInt(style.bottom, 10) / 100 * 45;
style.bottom = `calc(${style.bottom} - ${value}px)`;
}
if (style.right !== 'auto') {
if (utils.isPercentage(style.right)) {
const value = (1 - parseInt(style.right, 10) / 100) * 280;
style.right = `calc(${style.right} + ${value}px)`;
} else {
style.right = `calc(${style.right} + 280px)`;
}
}
if (style.left !== 'auto' && utils.isPercentage(style.left)) {
const value = parseInt(style.left, 10) / 100 * 280;
style.left = `calc(${style.left} - ${value}px)`;
}
}
}
}
onMouseOver (event) {
const {dragging, overed, selected, overElement, element} = this.props;
if (!dragging) {
event.stopPropagation();
clearTimeout(this.outTimeout);
if (!overed && !selected) {
overElement(element.id);
}
}
}
onMouseOut () {
const {dragging, overed} = this.props;
if (!dragging && overed) {
this.outTimeout = setTimeout(::this.selectOut, 50);
}
}
selectOut () {
const {outElement, element} = this.props;
outElement(element.id);
}
render () {
const {editing, settings, element, positionInParent, selected} = this.props;
let result;
if (editing && settings.drag) {
const draggableProps = Object.assign({
dragInfo: {
type: 'move',
id: element.id,
parentId: element.parent,
positionInParent
},
onClick: ::this.onElementClick,
type: element.tag,
disabled: (selected && settings.drag.dragSelected === false)
}, settings.drag);
result = (
{this.renderTag()}
);
} else {
result = this.renderTag();
}
return result;
}
renderTag () {
const HtmlTag = this.props.htmlTag;
const {style, className, editing, element} = this.props;
const calcStyle = Object.assign({}, style);
this.processAnimationStyle(calcStyle);
this.processPosition(calcStyle);
const tagProps = {
style: calcStyle,
className
};
if (editing) {
tagProps.onMouseOver = ::this.onMouseOver;
tagProps.onMouseOut = ::this.onMouseOut;
tagProps.ref = (ref) => {this.ref = ref;};
tagProps.id = element.id;
}
return (
{this.renderContent()}
{this.renderHighlight()}
);
}
renderContent () {
const {editing, settings, element} = this.props;
let result;
if (editing && settings.drop && !settings.drop.customDropArea) {
const droppableProps = Object.assign({
dropInfo: {
id: element.id
},
type: element.tag,
placeholder: true,
placeholderRender: ::this.renderPlaceholder
}, settings.drop);
result = (
{this.props.children}
);
} else {
result = this.props.children;
}
return result;
}
renderPlaceholder (options) {
const {settings, element} = this.props;
return (
);
}
renderHighlight () {
const {editing, selected, overed, dragging, element, settings} = this.props;
if (editing && (selected || overed) && !dragging && this.ref) {
return (
);
}
}
}
================================================
FILE: lib/shared/elements/element/empty.jsx
================================================
import Component from 'components/component';
import React, {PropTypes} from 'react';
import styles from './empty.less';
export default class Empty extends Component {
static propTypes = {
settings: PropTypes.object.isRequired,
element: PropTypes.object.isRequired,
spotClick: PropTypes.func.isRequired
};
onClick () {
this.props.spotClick(0, this.refs.button);
}
render () {
const {settings, element} = this.props;
return (
{settings.icon.content}
{`${element.label || element.tag} is empty`}
Drop elements here or
);
}
}
================================================
FILE: lib/shared/elements/element/empty.less
================================================
@import '~styles/colors.less';
.root {
text-align: center;
border: 1px solid #efefef;
min-height: 150px;
}
.info {
margin-top: 35px;
:global {
i, span {
display: inline-block;
vertical-align: top;
line-height: 36px;
}
i {
font-size: 14px;
color: @primary;
margin-right: 10px;
}
span {
font-size: 14px;
text-transform: uppercase;
color: @primary;
}
}
}
.actions {
font-size: 12px;
color: @adminText;
}
.addButton {
color: @primary;
text-decoration: underline;
}
================================================
FILE: lib/shared/elements/element/highlight.jsx
================================================
import cx from 'classnames';
import Component from 'components/component';
import Portal from 'components/portal';
import React, {PropTypes} from 'react';
import styles from './highlight.less';
import ContextMenu from './context-menu';
export default class Highlight extends Component {
static propTypes = {
selected: PropTypes.bool.isRequired,
element: PropTypes.object.isRequired,
settings: PropTypes.object.isRequired,
dom: PropTypes.node.isRequired
};
componentDidMount () {
this.mousewheelevt = (/Firefox/i.test(navigator.userAgent)) ? 'DOMMouseScroll' : 'mousewheel';
this.scrollBind = ::this.onScroll;
this.resizeBind = ::this.onResize;
document.body.addEventListener(this.mousewheelevt, this.scrollBind, false);
window.addEventListener('resize', this.resizeBind, false);
this.updateTimeoutInterval = setInterval(::this.updatePosition, 30);
}
componentWillUnmount () {
document.body.removeEventListener(this.mousewheelevt, this.scrollBind);
window.removeEventListener('resize', this.resizeBind);
clearInterval(this.updateTimeoutInterval);
clearTimeout(this.updateTimeout);
}
onScroll () {
this.updatePosition();
this.updateTimeout = setTimeout(::this.updatePosition, 0);
}
onResize () {
this.updatePosition();
this.updateTimeout = setTimeout(::this.updatePosition, 10);
}
updatePosition () {
this.forceUpdate();
}
getPosition () {
const rect = this.props.dom.getBoundingClientRect();
return {
left: rect.left,
top: rect.top,
width: rect.width || (rect.right - rect.left),
height: rect.height || (rect.bottom - rect.top)
};
}
render () {
const {selected, element, settings} = this.props;
const style = this.getPosition();
return (
{settings.icon.content}
{element.label || element.tag}
{selected && style.height > 30 && this.renderContext()}
);
}
renderContext () {
const {element} = this.props;
return (
);
}
}
================================================
FILE: lib/shared/elements/element/highlight.less
================================================
@import '~styles/colors.less';
@borderSize: 1px;
.root {
position: absolute;
top: 0; left: 0; right: 0; bottom: 0;
pointer-events: none;
border: @borderSize solid @primary;
background-color: fade(@primary, 10%);
border-radius: 1px;
z-index: 1;
color: #ffffff;
}
.selected {
border: 1px solid @primary;
background-color: transparent;
&:before {
content: '';
display: inline-block;
position: absolute;
top: 0px; right: 0px; bottom: 0px; left: 0px;
border: 2px solid fade(@primary, 10%);
}
.identifier {
background-color: @primary;
}
}
.inside {
bottom: auto;
top: 0px;
left: 0px;
border-radius: 0px 0px 1px 0;
}
.symbol {
border-color: #ff9f00;
background-color: fade(#ff9f00, 10%);
.element-identifier {
background-color: #ff9f00;
}
&.selected {
border-color: #ff9f00;
&:before {
border: 2px solid fade(#ff9f00, 10%);
}
.identifier {
background-color: #ff9f00;
}
}
}
.identifier {
display: inline-block;
position: absolute;
top: -14px - @borderSize;
left: 0 - @borderSize;
font-size: 11px;
line-height: 15px;
padding: 0px 3px;
background-color: @primary;
border-radius: 1px 1px 0 0;
:global {
i, span {
display: inline-block;
vertical-align: top;
}
i {
margin-right: 3px;
font-size: 10px;
line-height: 15px;
color: #ffffff;
}
span {
line-height: 15px;
color: #ffffff;
font-size: 10px;
letter-spacing: 0.3px;
}
}
}
================================================
FILE: lib/shared/elements/element/index.js
================================================
import Component from 'components/component';
import React, {PropTypes} from 'react';
import {selectElement, overElement, outElement} from 'actions/page-builder';
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
import Element from './element';
@connect(
(state) => ({
editing: state.pageBuilder.editing,
overedId: state.pageBuilder.overedId,
selectedId: state.pageBuilder.selectedId,
display: state.display,
dragging: state.dnd.dragging,
building: state.router.location.query.build && true
}),
(dispatch) => bindActionCreators({
selectElement,
overElement,
outElement
}, dispatch)
)
export default class ElementContainer extends Component {
static propTypes = {
children: PropTypes.node,
element: PropTypes.object.isRequired,
editing: PropTypes.bool.isRequired,
overedId: PropTypes.string,
selectedId: PropTypes.string,
building: PropTypes.bool
};
getInitState () {
const {element} = this.props;
return {
animation: element.animation && element.animation.use,
animated: false,
animatedEditing: false
};
}
componentWillReceiveProps () {
const {editing, element} = this.props;
if (editing && this.state.animation !== (element.animation && element.animation.use)) {
this.setState({
animation: element.animation && element.animation.use
});
}
}
startAnimation () {
this.setState({
animated: true,
animatedEditing: false
});
}
resetAnimation () {
this.setState({
animated: false,
animatedEditing: true
});
}
render () {
const {overedId, selectedId, element, building} = this.props;
return (
{this.props.children}
);
}
}
================================================
FILE: lib/shared/elements/form/index.jsx
================================================
import forEach from 'lodash.foreach';
import request from 'superagent';
import warning from 'warning';
import React, {PropTypes} from 'react';
import {findDOMNode} from 'react-dom';
import propsSchema from './props-schema';
import settings from './settings';
import Component from '../component';
import Element from '../element';
// import slugify from 'slug';
export default class Form extends Component {
static propTypes = {
action: PropTypes.string,
schema: PropTypes.string,
custom: PropTypes.string,
children: PropTypes.node,
relax: PropTypes.object.isRequired
};
static propsSchema = propsSchema;
static settings = settings;
sendEmail (formData) {
request
.post('/send-email')
.set('Content-Type', 'application/json')
.type('json')
.send(formData)
.end((error, res) => {
warning(false, error);
warning(false, res);
});
}
addToSchema () {
// arg: formData
// let actions = schemaEntriesActionsFactory(this.props.schema);
//
// // Check required fields
// if (formData._title && !formData._slug) {
// formData._slug = slugify(formData._title, {lower: true}).toLowerCase();
// }
//
// actions
// .add(formData)
// .then((result) => {
//
// })
// .catch(() => {
//
// });
}
sendCustom () {
// arg: formData
// $
// .post(this.props.custom, formData)
// .done((response) => {
//
// })
// .fail((error) => {
//
// });
}
onSubmit (event) {
event.preventDefault();
const formElement = findDOMNode(this);
const formData = {};
forEach(formElement.elements, (element) => {
formData[element.name] = element.value;
});
if (this.props.action === 'email') {
this.sendEmail(formData);
} else if (this.props.action === 'schema') {
this.addToSchema(formData);
} else if (this.props.action === 'custom') {
this.sendCustom(formData);
}
}
render () {
return (
);
}
}
================================================
FILE: lib/shared/elements/form/props-schema.js
================================================
import React from 'react';
export default [
{
label: 'Action',
type: 'Select',
id: 'action',
props: {
labels: ['Send e-mail', 'Schema entry', 'Custom endpoint'],
values: ['email', 'schema', 'custom']
},
unlocks: {
schema: [
{
label: 'Schema',
type: 'SelectEntry',
id: 'schema',
props: {
// store: schemasStore
}
}
],
custom: [
{
label: 'Custom url',
type: 'String',
id: 'custom'
}
]
}
},
{
label: false,
id: 'linkDataButton',
type: 'Button',
props: {
label: (
radio_button_checked
Link data
),
action: 'linkFormData'
}
}
];
================================================
FILE: lib/shared/elements/form/settings.js
================================================
export default {
icon: {
class: 'nc-icon-mini ui-1_email-84'
},
category: 'form',
drop: {
rejects: 'Section'
},
drag: {}
};
================================================
FILE: lib/shared/elements/gap/index.jsx
================================================
import React, {PropTypes} from 'react';
import propsSchema from './props-schema';
import settings from './settings';
import Component from '../component';
import Element from '../element';
export default class Gap extends Component {
static propTypes = {
amount: PropTypes.number.isRequired,
relax: PropTypes.object.isRequired
};
static defaultProps = {
amount: '30px'
};
static propsSchema = propsSchema;
static settings = settings;
render () {
const style = {
height: this.props.amount
};
return (
);
}
}
================================================
FILE: lib/shared/elements/gap/props-schema.js
================================================
export default [
{
label: 'Size',
type: 'Pixels',
id: 'amount'
}
];
================================================
FILE: lib/shared/elements/gap/settings.js
================================================
export default {
icon: {
class: 'nc-icon-mini ui-1_bold-delete'
},
category: 'structure',
drop: false,
drag: {}
};
================================================
FILE: lib/shared/elements/google-maps/index.jsx
================================================
import Utils from 'helpers/utils';
import React, {PropTypes} from 'react';
import {GoogleMap, GoogleMapLoader, Marker} from 'react-google-maps';
import propsSchema from './props-schema';
import settings from './settings';
import Component from '../component';
import Element from '../element';
export default class GoogleMapsElem extends Component {
static propTypes = {
zoom: PropTypes.number.isRequired,
lat: PropTypes.string.isRequired,
lng: PropTypes.string.isRequired,
height: PropTypes.number.isRequired,
scrollwheel: PropTypes.bool.isRequired,
zoomControls: PropTypes.bool.isRequired,
mapTypeControl: PropTypes.bool.isRequired,
streetViewControl: PropTypes.bool.isRequired,
useMarker: PropTypes.bool.isRequired,
relax: PropTypes.object.isRequired
};
static defaultProps = {
zoom: 0,
lat: '0',
lng: '0',
height: '250px',
scrollwheel: false,
zoomControls: true,
mapTypeControl: false,
streetViewControl: true,
useMarker: true
};
static propsSchema = propsSchema;
static settings = settings;
getInitState () {
return {
ready: this.loadAPI()
};
}
componentDidUpdate (prevProps) {
if (this.props.relax.editing && this.state.ready && prevProps.height !== this.props.height && this._map) {
window.google.maps.event.trigger(this._map, 'resize');
}
}
loadAPI () {
let result = false;
if (typeof document !== 'undefined') {
if (!Utils.hasClass(document.body, 'googleMapsInitiated') &&
!Utils.hasClass(document.body, 'googleMapsLoading')) {
Utils.addClass(document.body, 'googleMapsLoading');
window.googleMapsInitiated = () => {
Utils.removeClass(document.body, 'googleMapsLoading');
Utils.addClass(document.body, 'googleMapsInitiated');
/* jshint ignore:start */
window.dispatchEvent(new Event('googleMapsInitiated'));
/* jshint ignore:end */
};
const script = document.createElement('script');
script.type = 'text/javascript';
script.src = 'https://maps.googleapis.com/maps/api/js?v=3.exp&sensor=false&callback=googleMapsInitiated';
document.body.appendChild(script);
window.addEventListener('googleMapsInitiated', this.onReady.bind(this));
} else if (!Utils.hasClass(document.body, 'googleMapsInitiated')) {
window.addEventListener('googleMapsInitiated', this.onReady.bind(this));
} else {
result = true;
}
}
return result;
}
onReady () {
this.setState({
ready: true
});
}
render () {
return (
{this.renderMap()}
);
}
renderMap () {
if (this.state.ready) {
let result;
const editing = this.props.relax.editing;
const key =
this.props.zoom +
this.props.scrollwheel +
this.props.zoomControls +
this.props.streetViewControl +
this.props.mapTypeControl +
this.props.lat +
this.props.lng +
this.props.height;
const gmap = (
}
googleMapElement={
{this._map = map;}}
containerProps={{
style: {
height: this.props.height
}
}}
googleMapsApi={window.google.maps}
options={{
scrollwheel: this.props.scrollwheel,
zoomControl: this.props.zoomControls,
streetViewControl: this.props.streetViewControl,
mapTypeControl: this.props.mapTypeControl
}}
zoom={parseFloat(this.props.zoom, 10)}
center={{lat: parseFloat(this.props.lat, 10), lng: parseFloat(this.props.lng, 10)}}
>{this.renderMarker()}
}
/>
);
if (editing) {
result = (
);
} else {
result = gmap;
}
return result;
}
}
renderMarker () {
if (this.props.useMarker) {
const position = {
lat: parseFloat(this.props.lat, 10),
lng: parseFloat(this.props.lng, 10)
};
return (
);
}
}
}
================================================
FILE: lib/shared/elements/google-maps/props-schema.js
================================================
import React from 'react';
export default [
{
label: 'Height',
type: 'Pixels',
id: 'height'
},
{
type: 'Columns',
options: [
{
label: 'Latitude',
type: 'Number',
id: 'lat',
props: {
arrows: false,
min: false
}
},
{
label: 'Longitude',
type: 'Number',
id: 'lng',
props: {
arrows: false,
min: false
}
}
]
},
{
type: 'Columns',
options: [
{
label: 'Zoom',
type: 'Number',
id: 'zoom',
props: {
min: 0,
max: 21,
label:
}
},
{
label: 'Use scrollwheel',
type: 'Boolean',
id: 'scrollwheel'
}
]
},
{
type: 'Columns',
options: [
{
label: 'Use marker',
type: 'Boolean',
id: 'useMarker'
},
{
label: 'Zoom controls',
type: 'Boolean',
id: 'zoomControls'
}
]
},
{
type: 'Columns',
options: [
{
label: 'Map Type Controls',
type: 'Boolean',
id: 'mapTypeControl'
},
{
label: 'Streetview Control',
type: 'Boolean',
id: 'streetViewControl'
}
]
}
];
================================================
FILE: lib/shared/elements/google-maps/settings.js
================================================
export default {
icon: {
class: 'nc-icon-mini social_logo-google-plus'
},
category: 'media',
drop: false,
drag: {}
};
================================================
FILE: lib/shared/elements/icon/classes.js
================================================
import jss from 'helpers/stylesheet';
export default jss.createRules({
holder: {
display: 'inline-block',
textAlign: 'center'
}
});
================================================
FILE: lib/shared/elements/icon/index.jsx
================================================
import cx from 'classnames';
import React, {PropTypes} from 'react';
import classes from './classes';
import propsSchema from './props-schema';
import settings from './settings';
import style from './style';
import Component from '../component';
import Element from '../element';
export default class Icon extends Component {
static propTypes = {
icon: PropTypes.object.isRequired,
align: PropTypes.string.isRequired,
styleClassMap: PropTypes.object,
relax: PropTypes.object.isRequired
};
static defaultProps = {
icon: {
family: 'fontawesome',
className: 'fa fa-beer'
},
align: 'center'
};
static propsSchema = propsSchema;
static settings = settings;
static style = style;
render () {
const classMap = this.props.styleClassMap || {};
const props = {
htmlTag: 'div',
...this.props.relax,
settings,
style: {
textAlign: this.props.align
}
};
return (
{this.props.icon && this.props.icon.content}
);
}
}
================================================
FILE: lib/shared/elements/icon/props-schema.js
================================================
export default [
{
label: 'Icon',
type: 'Icon',
id: 'icon'
},
{
label: 'Alignment',
type: 'Select',
id: 'align',
props: {
labels: ['Left', 'Center', 'Right'],
values: ['left', 'center', 'right']
}
}
];
================================================
FILE: lib/shared/elements/icon/settings.js
================================================
export default {
icon: {
class: 'nc-icon-mini location_flag-points-31'
},
category: 'content',
drop: false,
drag: {}
};
================================================
FILE: lib/shared/elements/icon/style.js
================================================
import {getColorString} from 'helpers/colors';
export default {
type: 'icon',
options: [
{
label: 'Color',
type: 'Color',
id: 'color'
},
{
label: 'Font size',
id: 'size',
type: 'Number',
props: {
allowed: ['px', 'em', 'pt']
}
},
{
label: 'Use Background',
type: 'Optional',
id: 'background',
unlocks: [
{
label: 'Width',
type: 'Pixels',
id: 'width'
},
{
label: 'Height',
type: 'Pixels',
id: 'height'
},
{
label: 'Background Color',
type: 'Color',
id: 'backgroundColor'
},
{
label: 'Rounded Corners',
type: 'Corners',
id: 'corners'
}
]
}
],
defaults: {
color: {
value: '#ffffff',
opacity: 100
},
size: '16px',
background: false,
width: '70px',
height: '70px',
backgroundColor: {
value: '#000000',
opacity: 100
},
corners: '0px'
},
rules: (props) => {
const rules = {
icon: {}
};
rules.icon.fontSize = props.size;
rules.icon.color = getColorString(props.color);
if (props.background) {
rules.holder = {};
rules.holder.width = props.width;
rules.holder.height = props.height;
rules.holder.backgroundColor = getColorString(props.backgroundColor);
rules.holder.borderRadius = props.corners;
rules.icon.lineHeight = props.height;
}
return rules;
},
getIdentifierLabel: (props) => {
let str = '';
str += props.size;
str += ' | ';
str += getColorString(props.color);
return str;
}
};
================================================
FILE: lib/shared/elements/image/classes.js
================================================
import jss from 'helpers/stylesheet';
export default jss.createRules({
overable: {
'.over-image': {
display: 'none'
},
':hover': {
'.normal-image': {
display: 'none'
},
'.over-image': {
display: 'inline-block'
}
}
}
});
================================================
FILE: lib/shared/elements/image/index.jsx
================================================
import cx from 'classnames';
import elementStyles from 'styles/element.less';
import MediaImage from 'components/image';
import Utils from 'helpers/utils';
import React, {PropTypes} from 'react';
import {getColorString} from 'helpers/colors';
import {findDOMNode} from 'react-dom';
import classes from './classes';
import propsSchema from './props-schema';
import settings from './settings';
import Component from '../component';
import Element from '../element';
export default class Image extends Component {
static propTypes = {
color: PropTypes.object.isRequired,
useOver: PropTypes.bool.isRequired,
imageOver: PropTypes.string,
strictHeight: PropTypes.bool.isRequired,
height: PropTypes.number.isRequired,
vertical: PropTypes.number.isRequired,
useMaxWidth: PropTypes.bool.isRequired,
width: PropTypes.number.isRequired,
horizontal: PropTypes.oneOf(['left', 'center', 'right']).isRequired,
children: PropTypes.string,
relax: PropTypes.object.isRequired
};
static defaultProps = {
color: {
value: '#ffffff',
opacity: 0
},
useOver: false,
strictHeight: false,
height: '200px',
vertical: '50%',
useMaxWidth: false,
width: '300px',
horizontal: 'center'
};
static propsSchema = propsSchema;
static settings = settings;
getInitState () {
return {
mounted: false
};
}
componentDidMount () {
const dom = findDOMNode(this);
const rect = dom.getBoundingClientRect();
const width = Math.round(rect.right - rect.left);
this.setState({
mounted: true,
width
});
}
render () {
const style = {
backgroundColor: getColorString(this.props.color)
};
const imageStyle = {};
if (this.props.strictHeight) {
style.height = this.props.height;
style.overflow = 'hidden';
Utils.translate(imageStyle, 0, `-${this.props.vertical}`);
imageStyle.top = parseInt(this.props.height, 10) * (parseInt(this.props.vertical, 10) / 100);
imageStyle.position = 'relative';
}
if (this.props.useMaxWidth) {
imageStyle.maxWidth = this.props.width;
style.textAlign = this.props.horizontal;
} else {
imageStyle.minWidth = '100%';
}
return (
{this.renderImage(imageStyle)}
);
}
renderImage (imageStyle) {
let result;
if (this.state.mounted && this.props.children) {
result = (
{this.renderOverImage(imageStyle)}
);
} else if (this.props.relax.editing) {
result = (
);
}
return result;
}
renderOverImage (imageStyle) {
if (this.props.useOver) {
return (
);
}
}
}
================================================
FILE: lib/shared/elements/image/props-schema.js
================================================
export default [
{
label: 'Background color',
type: 'Color',
id: 'color'
},
{
label: 'Image',
type: 'Image',
id: 'children'
},
{
label: 'On mouse over',
type: 'Optional',
id: 'useOver',
unlocks: [
{
type: 'Image',
id: 'imageOver'
}
]
},
{
label: 'Strict height',
type: 'Optional',
id: 'strictHeight',
unlocks: [
{
label: 'Pixels',
type: 'Pixels',
id: 'height'
},
{
label: 'Vertical position',
type: 'Percentage',
id: 'vertical'
}
]
},
{
label: 'Max Width',
type: 'Optional',
id: 'useMaxWidth',
unlocks: [
{
label: 'Pixels',
type: 'Pixels',
id: 'width'
},
{
label: 'Horizontal alignment',
type: 'Select',
id: 'horizontal',
props: {
labels: ['Left', 'Center', 'Right'],
values: ['left', 'center', 'right']
}
}
]
}
];
================================================
FILE: lib/shared/elements/image/settings.js
================================================
export default {
icon: {
class: 'nc-icon-mini media-1_image-02'
},
category: 'media',
drop: false,
drag: {}
};
================================================
FILE: lib/shared/elements/index.js
================================================
import Button from './button';
import Column from './column';
import Columns from './columns';
import Container from './container';
import Counter from './counter';
import Date from './date';
import DynamicList from './dynamic-list';
import Form from './form';
import Gap from './gap';
import GoogleMaps from './google-maps';
import Icon from './icon';
import Image from './image';
import LineDivider from './line-divider';
import Menu from './menu';
import MusicPlayer from './music-player';
import Section from './section';
import Symbol from './symbol';
import Textarea from './textarea';
import TextBox from './text-box';
import TextInput from './text-input';
import Video from './video';
export default {
Button,
Columns,
Column,
Container,
Counter,
Form,
Gap,
GoogleMaps,
Icon,
Image,
LineDivider,
Menu,
MusicPlayer,
DynamicList,
Section,
TextBox,
TextInput,
Textarea,
Video,
Symbol,
Date
};
================================================
FILE: lib/shared/elements/line-divider/classes.js
================================================
import jss from 'helpers/stylesheet';
export default jss.createRules({
holder: {
height: '1px'
},
line: {
borderBottom: '1px solid #000000'
}
});
================================================
FILE: lib/shared/elements/line-divider/index.jsx
================================================
import cx from 'classnames';
import React, {PropTypes} from 'react';
import classes from './classes';
import propsSchema from './props-schema';
import settings from './settings';
import style from './style';
import Component from '../component';
import Element from '../element';
export default class LineDivider extends Component {
static propTypes = {
styleClassMap: PropTypes.object,
relax: PropTypes.object.isRequired
};
static propsSchema = propsSchema;
static settings = settings;
static style = style;
render () {
const classMap = this.props.styleClassMap || {};
return (
);
}
}
================================================
FILE: lib/shared/elements/line-divider/props-schema.js
================================================
export default [];
================================================
FILE: lib/shared/elements/line-divider/settings.js
================================================
export default {
icon: {
class: 'nc-icon-mini ui-1_simple-delete'
},
category: 'structure',
style: 'lineDivider',
drop: false,
drag: {}
};
================================================
FILE: lib/shared/elements/line-divider/style.js
================================================
import {getColorString} from 'helpers/colors';
export default {
type: 'lineDivider',
options: [
{
type: 'Columns',
options: [
{
label: 'Line Height',
type: 'Pixels',
id: 'size'
},
{
label: 'Style',
type: 'LineStyle',
id: 'style'
}
]
},
{
label: 'Color',
type: 'Color',
id: 'color'
},
{
label: 'Max Width',
type: 'Optional',
id: 'useMaxWidth',
unlocks: [
{
label: 'Max Width',
type: 'Pixels',
id: 'maxWidth'
},
{
label: 'Align',
type: 'Select',
id: 'align',
props: {
labels: ['Left', 'Center', 'Right'],
values: ['left', 'center', 'right']
}
}
]
},
{
label: 'Padding',
type: 'Padding',
id: 'padding'
}
],
defaults: {
size: 1,
style: 'solid',
color: {
value: '#000000',
opacity: 100
},
useMaxWidth: false,
maxWidth: '100px',
align: 'center',
padding: '5px 0px 10px 0px'
},
rules: (props) => {
const rules = {
line: {},
holder: {}
};
rules.line.borderBottom = `${props.size} ${props.style} ${getColorString(props.color)}`;
rules.holder.height = props.size;
rules.holder.padding = props.padding;
if (props.useMaxWidth) {
rules.line.display = 'inline-block';
rules.line.width = props.maxWidth;
rules.line.maxWidth = '100%';
rules.line.verticalAlign = 'top';
rules.holder.textAlign = props.align;
}
return rules;
},
getIdentifierLabel: (props) => `${props.size}px | ${props.width}`
};
================================================
FILE: lib/shared/elements/menu/classes.js
================================================
import jss from 'helpers/stylesheet';
export default jss.createRules({
menu: {
listStyleType: 'none',
padding: '0px',
margin: '0px',
textAlign: 'left',
'li:last-child': {
marginRight: '0px'
}
},
menuItem: {
display: 'inline-block',
position: 'relative',
verticalAlign: 'top',
marginRight: '20px'
},
button: {
textDecoration: 'none',
display: 'inline-block',
cursor: 'pointer'
},
submenu: {
position: 'absolute',
top: '100%',
left: 0
},
submenuItem: {
display: 'block',
textAlign: 'left'
},
submenuButton: {
display: 'block',
textDecoration: 'none',
cursor: 'pointer'
}
});
================================================
FILE: lib/shared/elements/menu/entry.jsx
================================================
import cx from 'classnames';
import A from 'components/a';
import Animate from 'components/animate';
import Component from 'components/component';
import React, {PropTypes} from 'react';
export default class Entry extends Component {
static propTypes = {
entry: PropTypes.object.isRequired,
subitem: PropTypes.bool.isRequired,
classMap: PropTypes.object,
classes: PropTypes.object,
pageBuilder: PropTypes.object
};
getInitState () {
return {
opened: false
};
}
componentWillUnmount () {
clearTimeout(this.closeTimeout);
}
onMouseOver () {
clearTimeout(this.closeTimeout);
this.setState({
opened: true
});
}
onMouseOut () {
this.closeTimeout = setTimeout(::this.close, 400);
}
close () {
this.setState({
opened: false
});
}
render () {
let label;
let href;
if (this.props.entry.type === 'page') {
label = this.props.entry.page && this.props.entry.page.title;
href = `/${this.props.entry.page && this.props.entry.page.slug}`;
} else if (this.props.entry.type === 'link') {
label = this.props.entry.link.label;
href = this.props.entry.link.url;
}
const className = cx(
!this.props.subitem ? this.props.classes.menuItem : this.props.classes.submenuItem,
!this.props.subitem ? this.props.classMap.entry : this.props.classMap.submenuEntry
);
return (
{this.renderEntryLink(href, label)}
{this.renderEntryChildren()}
);
}
renderEntryChildren () {
// This menu only supports 2 levels
if (!this.props.subitem &&
this.props.entry.children &&
this.props.entry.children.length > 0 &&
this.state.opened) {
return (
{this.props.entry.children.map(this.renderEntry, this)}
);
}
}
renderEntry (entry) {
return (
);
}
renderEntryLink (href, label) {
let result;
const linkClasses = cx(
!this.props.subitem ? this.props.classes.button : this.props.classes.submenuButton,
!this.props.subitem ? this.props.classMap.button : this.props.classMap.submenuButton
);
if (this.props.pageBuilder && this.props.pageBuilder.editing) {
result = (
{label}
);
} else {
result = (
{label}
);
}
return result;
}
}
================================================
FILE: lib/shared/elements/menu/index.jsx
================================================
import * as elementsActions from 'actions/elements';
import React, {PropTypes} from 'react';
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
import {buildQueryAndVariables} from 'relax-fragments';
import propsSchema from './props-schema';
import settings from './settings';
import style from './style';
import Component from '../component';
import Element from '../element';
import Menu from './menu';
const menuDataFragment = {
id: 1,
type: 1,
page: {
_id: 1,
title: 1
},
link: {
label: 1,
url: 1
}
};
@connect(
(state) => ({
elements: state.elements
}),
(dispatch) => bindActionCreators(elementsActions, dispatch)
)
export default class MenuContainer extends Component {
static fragments = {
menu: {
data: {
...menuDataFragment,
children: {
...menuDataFragment
}
}
}
};
static propTypes = {
menuId: PropTypes.string,
relax: PropTypes.object.isRequired,
elements: PropTypes.object.isRequired
};
static propsSchema = propsSchema;
static settings = settings;
static style = style;
getInitState () {
this.fetchData(this.props);
return {};
}
componentWillReceiveProps (nextProps) {
if (this.props.relax.editing && nextProps.menuId !== this.props.menuId) {
this.fetchData(nextProps);
}
}
fetchData (props) {
if (props.menuId) {
props.getElementData(props.elementId, buildQueryAndVariables(
this.constructor.fragments,
{
menu: {
_id: {
value: props.menuId,
type: 'ID!'
}
}
}
));
}
}
render () {
const {elements, relax} = this.props;
const menu = elements[relax.element.id] && elements[relax.element.id].menu;
return (
);
}
}
================================================
FILE: lib/shared/elements/menu/menu.jsx
================================================
import cx from 'classnames';
import Component from 'components/component';
import React, {PropTypes} from 'react';
import classes from './classes';
import Entry from './entry';
export default class Menu extends Component {
static propTypes = {
menu: PropTypes.object,
styleClassMap: PropTypes.object,
pageBuilder: PropTypes.object
};
static defaultProps = {
styleClassMap: {}
};
render () {
const classMap = this.props.styleClassMap;
let result;
if (this.props.menu && this.props.menu.data) {
result = (
{this.props.menu.data.map(this.renderEntry, this)}
);
} else if (this.context.editing) {
result = (
Choose a menu on settings
);
} else {
result = ;
}
return result;
}
renderEntry (entry) {
return (
);
}
}
================================================
FILE: lib/shared/elements/menu/props-schema.js
================================================
export default [
{
label: 'Menu',
type: 'MenuPicker',
id: 'menuId'
}
];
================================================
FILE: lib/shared/elements/menu/settings.js
================================================
export default {
icon: {
class: 'nc-icon-mini ui-2_menu-bold'
},
category: 'structure',
drop: false,
drag: {}
};
================================================
FILE: lib/shared/elements/menu/style.js
================================================
import Utils from 'helpers/utils';
import {getColorString} from 'helpers/colors';
export default {
type: 'menu',
options: [
{
label: 'Layout',
type: 'Section',
id: 'layoutSection',
unlocks: [
{
label: 'Distance between buttons',
id: 'distance',
type: 'Pixels'
},
{
label: 'Buttons Alignment',
id: 'alignment',
type: 'Select',
props: {
labels: ['Left', 'Center', 'Right'],
values: ['left', 'center', 'right']
}
}
]
},
{
label: 'Buttons Text',
type: 'Section',
id: 'textSection',
unlocks: [
{
label: 'Font Family',
id: 'font',
type: 'Font'
},
{
label: 'Font Size',
id: 'fontSize',
type: 'Pixels'
},
{
label: 'Line Height',
id: 'lineHeight',
type: 'Pixels'
},
{
label: 'Letter Spacing',
id: 'letterSpacing',
type: 'Pixels'
},
{
label: 'Color',
id: 'color',
type: 'Color'
},
{
label: 'Links color hover',
id: 'colorOver',
type: 'Color'
},
{
label: 'Underline',
id: 'underline',
type: 'Boolean'
}
]
},
{
label: 'Buttons Background',
type: 'Section',
id: 'backgroundSection',
unlocks: [
{
label: 'Use Background',
type: 'Optional',
id: 'useBackground',
unlocks: [
{
label: 'Background Color',
type: 'Color',
id: 'backgroundColor'
},
{
label: 'Background Color on over',
type: 'Color',
id: 'backgroundColorOver'
}
]
},
{
label: 'Border',
type: 'Optional',
id: 'useBorder',
unlocks: [
{
type: 'Border',
id: 'border'
},
{
label: 'Border Color on over',
type: 'Color',
id: 'borderColorOver'
}
]
},
{
label: 'Rounded Corners',
type: 'Optional',
id: 'useCorners',
unlocks: [
{
type: 'Corners',
id: 'corners'
}
]
},
{
label: 'Padding',
type: 'Optional',
id: 'usePadding',
unlocks: [
{
type: 'Padding',
id: 'padding'
}
]
}
]
},
{
label: 'Submenus Layout',
type: 'Section',
id: 'submenuLayoutSection',
unlocks: [
{
label: 'Snap submenu (relative to main button)',
id: 'submenuAlignment',
type: 'Select',
props: {
labels: ['Left', 'Center', 'Right'],
values: ['left', 'center', 'right']
}
},
{
label: 'Vertical Offset',
id: 'submenuVertical',
type: 'Pixels'
},
{
label: 'Horizontal Offset',
id: 'submenuHorizontal',
type: 'Pixels'
},
{
label: 'Buttons Alignment',
id: 'submenuButtonsAlignment',
type: 'Select',
props: {
labels: ['Left', 'Center', 'Right'],
values: ['left', 'center', 'right']
}
}
]
},
{
label: 'Submenu Indicator',
type: 'Section',
id: 'submenuIndicatorSection',
unlocks: [
{
label: 'Submenu Indicator Icon',
id: 'submenuIndicatorIcon',
type: 'Icon'
},
{
label: 'Distance to text',
id: 'submenuIndicatorDistance',
type: 'Pixels'
},
{
label: 'Icon font Size',
id: 'submenuIndicatorSize',
type: 'Pixels'
}
]
},
{
label: 'Submenus Background',
type: 'Section',
id: 'submenuBackgroundSection',
unlocks: [
{
label: 'Background Color',
type: 'Color',
id: 'submenuBackgroundColor'
},
{
label: 'Padding',
type: 'Optional',
id: 'submenuUsePadding',
unlocks: [
{
type: 'Padding',
id: 'submenuPadding'
}
]
},
{
label: 'Border',
type: 'Optional',
id: 'submenuUseBorder',
unlocks: [
{
type: 'Border',
id: 'submenuBorder'
}
]
},
{
label: 'Rounded Corners',
type: 'Optional',
id: 'submenuUseCorners',
unlocks: [
{
type: 'Corners',
id: 'submenuCorners'
}
]
}
]
},
{
label: 'Submenus Buttons Text',
type: 'Section',
id: 'submenuButtonsTextSection',
unlocks: [
{
label: 'Font Family',
id: 'submenuButtonsFont',
type: 'Font'
},
{
label: 'Font Size',
id: 'submenuButtonsFontSize',
type: 'Pixels'
},
{
label: 'Line Height',
id: 'submenuButtonsLineHeight',
type: 'Pixels'
},
{
label: 'Letter Spacing',
id: 'submenuButtonsLetterSpacing',
type: 'Pixels'
},
{
label: 'Color',
id: 'submenuButtonsColor',
type: 'Color'
},
{
label: 'Links color hover',
id: 'submenuButtonsColorOver',
type: 'Color'
},
{
label: 'Underline',
id: 'submenuButtonsUnderline',
type: 'Boolean'
}
]
},
{
label: 'Submenus Buttons Background',
type: 'Section',
id: 'submenuButtonsBackgroundSection',
unlocks: [
{
label: 'Use Background',
type: 'Optional',
id: 'submenuButtonsUseBackground',
unlocks: [
{
label: 'Background Color',
type: 'Color',
id: 'submenuButtonsBackgroundColor'
},
{
label: 'Background Color on over',
type: 'Color',
id: 'submenuButtonsBackgroundColorOver'
}
]
},
{
label: 'Border',
type: 'Optional',
id: 'submenuButtonsUseBorder',
unlocks: [
{
type: 'Border',
id: 'submenuButtonsBorder'
},
{
label: 'Border Color on over',
type: 'Color',
id: 'submenuButtonsBorderColorOver'
}
]
},
{
label: 'Rounded Corners',
type: 'Optional',
id: 'submenuButtonsUseCorners',
unlocks: [
{
type: 'Corners',
id: 'submenuButtonsCorners'
}
]
},
{
label: 'Padding',
type: 'Optional',
id: 'submenuButtonsUsePadding',
unlocks: [
{
type: 'Padding',
id: 'submenuButtonsPadding'
}
]
}
]
}
],
defaults: {
distance: 20,
alignment: 'left',
font: {},
fontSize: 16,
lineHeight: 16,
letterSpacing: 0,
color: {
value: '#ffffff',
opacity: 100
},
colorOver: {
value: '#ffffff',
opacity: 100
},
underline: false,
useBackground: false,
backgroundColor: {
value: '#ffffff',
opacity: 100
},
backgroundColorOver: {
value: '#ffffff',
opacity: 100
},
useBorder: false,
border: false,
borderColorOver: {
value: '#ffffff',
opacity: 100
},
useCorners: false,
corners: '0px',
usePadding: false,
padding: '0px',
submenuAlignment: 'left',
submenuVertical: 10,
submenuHorizontal: 0,
submenuButtonsAlignment: 'left',
submenuBackgroundColor: {
value: '#333333',
opacity: 100
},
submenuUsePadding: false,
submenuPadding: '0px',
submenuUseBorder: false,
submenuBorder: {},
submenuUseCorners: false,
submenuCorners: '0px',
submenuButtonsFont: {},
submenuButtonsFontSize: 16,
submenuButtonsLineHeight: 16,
submenuButtonsLetterSpacing: 0,
submenuButtonsColor: {
value: '#ffffff',
opacity: 100
},
submenuButtonsButtonsColorOver: {
value: '#ffffff',
opacity: 100
},
submenuButtonsUnderline: false,
submenuButtonsUseBackground: false,
submenuButtonsBackgroundColor: {
value: '#ffffff',
opacity: 100
},
submenuButtonsBackgroundColorOver: {
value: '#ffffff',
opacity: 100
},
submenuButtonsUseBorder: false,
submenuButtonsBorder: false,
submenuButtonsBorderColorOver: {
value: '#ffffff',
opacity: 100
},
submenuButtonsUseCorners: false,
submenuButtonsCorners: '0px',
submenuButtonsUsePadding: false,
submenuButtonsPadding: '0px'
},
rules: (props) => {
const style = {
menu: {
textAlign: props.alignment
},
entry: {
marginRight: props.distance
},
button: {
fontSize: props.fontSize,
lineHeight: props.lineHeight,
letterSpacing: props.letterSpacing,
color: getColorString(props.color),
textDecoration: props.underline && 'underline',
backgroundColor: props.useBackground && getColorString(props.backgroundColor),
borderRadius: props.useCorners && props.corners,
padding: props.usePadding && props.padding,
':hover': {
color: getColorString(props.colorOver),
backgroundColor: props.useBackground && getColorString(props.backgroundColorOver),
borderColor: getColorString(props.borderColorOver)
}
},
submenu: {
backgroundColor: getColorString(props.submenuBackgroundColor),
padding: props.submenuUsePadding && props.submenuPadding,
borderRadius: props.submenuUseCorners && props.submenuCorners
},
submenuButton: {
fontSize: props.submenuButtonsFontSize,
lineHeight: props.submenuButtonsLineHeight,
letterSpacing: props.submenuButtonsLetterSpacing,
color: getColorString(props.submenuButtonsColor),
textDecoration: props.submenuButtonsUnderline && 'underline',
backgroundColor: props.submenuButtonsUseBackground && getColorString(props.submenuButtonsBackgroundColor),
borderRadius: props.submenuButtonsUseCorners && props.submenuButtonsCorners,
padding: props.submenuButtonsUsePadding && props.submenuButtonsPadding,
textAlign: props.submenuButtonsAlignment,
':hover': {
color: getColorString(props.submenuButtonsColorOver),
backgroundColor:
props.submenuButtonsUseBackground &&
getColorString(props.submenuButtonsBackgroundColorOver),
borderColor: getColorString(props.submenuButtonsBorderColorOver)
}
}
};
// Font
if (props.font && props.font.family && props.font.fvd) {
style.button.fontFamily = props.font.family;
Utils.processFVD(style.button, props.font.fvd);
}
// Border
if (props.useBorder) {
Utils.applyBorders(style.button, props.border);
}
// Submenu Border
if (props.submenuUseBorder) {
Utils.applyBorders(style.submenu, props.submenuBorder);
}
// Submenu Buttons Font
if (props.submenuButtonsFont && props.submenuButtonsFont.family && props.submenuButtonsFont.fvd) {
style.submenuButton.fontFamily = props.submenuButtonsFont.family;
Utils.processFVD(style.submenuButton, props.submenuButtonsFont.fvd);
}
// Submenu Buttons Border
if (props.submenuButtonsUseBorder) {
Utils.applyBorders(style.submenuButton, props.submenuButtonsBorder);
}
// Submenu alignment and Offset
style.submenu.transform = `translateY(${props.submenuVertical})`;
if (props.submenuAlignment === 'left') {
style.submenu.left = props.submenuHorizontal;
} else if (props.submenuAlignment === 'right') {
style.submenu.left = 'auto';
style.submenu.right = -props.submenuHorizontal;
} else if (props.submenuAlignment === 'center') {
style.submenu.left = '50%';
style.submenu.transform = `translateX(-50%) translateY(${props.submenuVertical})`;
}
return style;
}
};
================================================
FILE: lib/shared/elements/music-player/classes.js
================================================
import jss from 'helpers/stylesheet';
export default jss.createRules({
musicPlayer: {
position: 'relative',
backgroundColor: '#333333',
overflow: 'hidden',
'-webkit-mask-image': 'url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAIAAACQd1PeAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAA5JREFUeNpiYGBgAAgwAAAEAAGbA+oJAAAAAElFTkSuQmCC)'
},
wrapper: {
position: 'relative'
},
part: {
display: 'table-cell',
verticalAlign: 'middle'
},
table: {
display: 'table',
width: '100%'
},
fit: {
width: '1%',
whiteSpace: 'nowrap'
},
bar: {
position: 'relative',
overflow: 'hidden',
height: 7,
backgroundColor: '#cccccc'
},
streamBars: {
position: 'absolute',
top: 0,
left: 0,
bottom: 0,
width: 0,
cursor: 'pointer'
},
volume: {
width: 170
},
volumeBars: {
paddingLeft: 10
},
volumeBar: {
cursor: 'pointer'
},
divider: {
width: 1,
backgroundColor: '#3A3A3A'
}
});
================================================
FILE: lib/shared/elements/music-player/container.jsx
================================================
import request from 'superagent';
import Component from 'components/component';
import React, {PropTypes} from 'react';
import {soundManager} from 'soundmanager2';
import Player from './player';
const CONSUMER_KEY = '6c786345f5161898f1e1380802ce9226';
export default class PlayerContainer extends Component {
static propTypes = {
type: PropTypes.oneOf(['local', 'soundcloud']).isRequired,
soundcloud: PropTypes.string,
sound: PropTypes.string,
pageBuilder: PropTypes.object,
elementId: PropTypes.string.isRequired,
defaultVolume: PropTypes.number
};
getInitState () {
const editing = this.props.pageBuilder && this.props.pageBuilder.editing;
if (!editing && this.isClient()) {
if (this.props.type === 'soundcloud') {
this.loadSoundcloud();
}
}
return {
playing: false,
muted: false,
loadedPercentage: 0,
loadedLabel: '00:00',
playedPercentage: 0,
playedLabel: '00:00',
volume: this.props.defaultVolume
};
}
componentDidUpdate () {
// if (!this.props.pageBuilder.editing && !prevState.sound && this.state.sound) {
// this.url = this.state.sound.url;
// soundManager.onready(this.createSound.bind(this));
// }
}
componentWillUnmount () {
if (this.sound) {
this.sound.destruct();
}
}
loadSoundcloud () {
request
.get(`http://api.soundcloud.com/resolve?url=${this.props.soundcloud}&format=json&consumer_key=${CONSUMER_KEY}&callback=?`)
.set('Accept', 'application/json')
.end(::this.soundcloudLoaded);
}
soundcloudLoaded (err, soundcloudInfo) {
if (!err && soundcloudInfo && soundcloudInfo.stream_url) {
this.url = soundcloudInfo.stream_url;
if (this.url.indexOf('secret_token') === -1) {
this.url += '?';
} else {
this.url += '&';
}
this.url += `consumer_key=${CONSUMER_KEY}`;
soundManager.onready(::this.createSound);
}
}
createSound () {
this.sound = soundManager.createSound({
id: `sound_${this.props.elementId}`,
url: this.url,
autoLoad: false,
autoPlay: false,
onfinish: this.playingStatusChanged.bind(this),
whileloading: this.whileLoading.bind(this),
whileplaying: this.whilePlaying.bind(this),
volume: this.state.volume
});
this.playingStatusChanged();
}
whileLoading () {
const loadedPercentage = this.sound.bytesLoaded / this.sound.bytesTotal;
let secondsPassed = Math.round(this.sound.duration / 1000);
let minutesPassed = 0;
if (secondsPassed >= 60) {
minutesPassed = Math.floor(secondsPassed / 60);
secondsPassed = secondsPassed - minutesPassed * 60;
}
const minutesLabel = (minutesPassed < 10 ? `0${minutesPassed}` : minutesPassed);
const secondsLabel = (secondsPassed < 10 ? `0${secondsPassed}` : secondsPassed);
const loadedLabel = `${minutesLabel}:${secondsLabel}`;
this.setState({
loadedPercentage,
loadedLabel
});
}
whilePlaying () {
let playedPercentage;
if (this.sound.loaded) {
playedPercentage = this.sound.position / this.sound.duration;
} else {
playedPercentage = this.sound.position / this.sound.durationEstimate;
}
let secondsPassed = Math.round(this.sound.position / 1000);
let minutesPassed = 0;
if (secondsPassed >= 60) {
minutesPassed = Math.floor(secondsPassed / 60);
secondsPassed = secondsPassed - minutesPassed * 60;
}
const minutesLabel = (minutesPassed < 10 ? `0${minutesPassed}` : minutesPassed);
const secondsLabel = (secondsPassed < 10 ? `0${secondsPassed}` : secondsPassed);
const playedLabel = `${minutesLabel}:${secondsLabel}`;
this.setState({
playedPercentage,
playedLabel
});
}
playingStatusChanged () {
if (this.sound.paused || this.sound.playState === 0) {
this.setState({
playing: false
});
} else {
this.setState({
playing: true
});
}
}
togglePlay () {
this.sound.togglePause();
this.playingStatusChanged();
}
toggleMute () {
this.sound.toggleMute();
if (!this.sound.muted && this.sound.volume === 0) {
this.sound.setVolume(this.props.defaultVolume);
}
this.setState({
volume: this.sound.muted ? 0 : this.sound.volume,
muted: this.sound.muted || this.sound.volume === 0
});
}
setVolume (perc) {
this.sound.setVolume(perc);
if (this.sound.muted && this.sound.volume > 0) {
this.sound.unmute();
} else if (!this.sound.muted && this.sound.volume === 0) {
this.sound.mute();
}
this.setState({
volume: this.sound.muted ? 0 : this.sound.volume,
muted: this.sound.muted || this.sound.volume === 0
});
}
goTo (perc) {
this.sound.setPosition(perc * this.sound.duration);
}
render () {
return (
);
}
}
================================================
FILE: lib/shared/elements/music-player/index.jsx
================================================
import React, {PropTypes} from 'react';
import propsSchema from './props-schema';
import settings from './settings';
import style from './style';
import Component from '../component';
import Element from '../element';
import PlayerContainer from './container';
export default class MusicPlayer extends Component {
static propTypes = {
relax: PropTypes.object.isRequired
};
static defaultProps = {
type: 'local',
defaultVolume: 50
};
static propsSchema = propsSchema;
static settings = settings;
static style = style;
render () {
return (
);
}
}
================================================
FILE: lib/shared/elements/music-player/player.jsx
================================================
import cx from 'classnames';
import utils from 'helpers/utils';
import BackgroundImage from 'components/background-image';
import Component from 'components/component';
import React, {PropTypes} from 'react';
import classes from './classes';
export default class Player extends Component {
static propTypes = {
togglePlay: PropTypes.func.isRequired,
toggleMute: PropTypes.func.isRequired,
setVolume: PropTypes.func.isRequired,
goTo: PropTypes.func.isRequired,
styleClassMap: PropTypes.object,
display: PropTypes.string,
useBackgroundImage: PropTypes.bool,
backgroundImage: PropTypes.string,
repeat: PropTypes.string,
vertical: PropTypes.string,
horizontal: PropTypes.string,
opacity: PropTypes.number,
defaultVolume: PropTypes.number,
muted: PropTypes.bool.isRequired,
playing: PropTypes.bool.isRequired,
volume: PropTypes.number.isRequired,
playedLabel: PropTypes.string.isRequired,
loadedLabel: PropTypes.string.isRequired,
loadedPercentage: PropTypes.number.isRequired,
playedPercentage: PropTypes.number.isRequired,
editing: PropTypes.bool
};
static defaultProps = {
styleClassMap: {}
};
togglePlay (event) {
event.preventDefault();
this.props.togglePlay();
}
toggleMute (event) {
event.preventDefault();
this.props.toggleMute();
}
onProgressClick (event) {
event.preventDefault();
const bounds = utils.getOffsetRect(this.refs.bars);
const percStream = (event.pageX - bounds.left) / bounds.width;
this.props.goTo(percStream);
}
onVolumeClick (event) {
event.preventDefault();
const bounds = utils.getOffsetRect(this.refs.volume);
const percVolume = Math.round(((event.pageX - bounds.left) / bounds.width) * 100);
this.props.setVolume(percVolume);
}
render () {
const classMap = this.props.styleClassMap;
const displayVolume = this.props.display !== 'mobile' && this.props.display !== 'tablet';
return (
{this.renderBackground()}
{this.renderControls(classMap)}
{this.renderDivider(classMap)}
{this.renderPlayback(classMap)}
{displayVolume && this.renderDivider(classMap)}
{displayVolume && this.renderVolume(classMap)}
);
}
renderBackground () {
if (this.props.useBackgroundImage) {
return (
);
}
}
renderDivider (classMap) {
return (
);
}
renderVolume (classMap) {
return (
{this.renderVolumeBar(classMap)}
);
}
renderVolumeBar (classMap) {
const volume = this.props.editing ? this.props.defaultVolume : this.props.volume;
const activeStyle = {
width: `${volume}%`
};
return (
);
}
renderPlayback (classMap) {
return (
Text 1
Text 2
{this.props.playedLabel}
{this.renderProgressBar(classMap)}
{this.props.loadedLabel}
);
}
renderProgressBar (classMap) {
const streamStyle = {
width: `${this.props.loadedPercentage * 100}%`
};
const activeStyle = {
width: `${this.props.playedPercentage * 100}%`
};
return (
);
}
renderControls (classMap) {
return (
);
}
}
================================================
FILE: lib/shared/elements/music-player/props-schema.js
================================================
export default [
{
label: 'Music',
type: 'Select',
id: 'type',
props: {
labels: ['Your Sounds', 'Soundcloud'],
values: ['local', 'soundcloud']
},
unlocks: {
local: [
{
label: 'Sound',
type: 'Audio',
id: 'sound'
}
],
soundcloud: [
{
label: 'Soundcloud music url',
type: 'String',
id: 'soundcloud'
}
]
}
},
{
label: 'Default volume',
type: 'Percentage',
id: 'defaultVolume'
},
{
label: 'Background Image',
type: 'Optional',
id: 'useBackgroundImage',
unlocks: [
{
type: 'Image',
id: 'backgroundImage',
unlocks: [
{
label: 'Repeat',
type: 'Select',
id: 'repeat',
props: {
labels: ['No repeat', 'Repeat', 'Repeat horiz.', 'Repeat vert.'],
values: ['no-repeat', 'repeat', 'repeat-x', 'repeat-y']
}
},
{
label: 'Opacity',
type: 'Percentage',
id: 'opacity'
},
{
label: 'Vertical position',
type: 'Percentage',
id: 'vertical'
},
{
label: 'Horizontal position',
type: 'Percentage',
id: 'horizontal'
}
]
}
]
}
];
================================================
FILE: lib/shared/elements/music-player/settings.js
================================================
export default {
icon: {
class: 'nc-icon-mini media-2_note-03'
},
category: 'media',
drop: false,
drag: {}
};
================================================
FILE: lib/shared/elements/music-player/style.js
================================================
import Utils from 'helpers/utils';
import {getColorString} from 'helpers/colors';
export default {
type: 'musicplayer',
options: [
{
label: 'Background',
type: 'Section',
id: 'backgroundSection',
unlocks: [
{
label: 'Background Color',
type: 'Color',
id: 'backgroundColor'
},
{
label: 'Padding',
type: 'Padding',
id: 'padding'
},
{
label: 'Border',
type: 'Optional',
id: 'useBorder',
unlocks: [
{
type: 'Border',
id: 'border'
}
]
},
{
label: 'Rounded Corners',
type: 'Optional',
id: 'useCorners',
unlocks: [
{
type: 'Corners',
id: 'corners'
}
]
}
]
},
{
label: 'Layout',
type: 'Section',
id: 'layoutSection',
unlocks: [
{
label: 'Controls area padding',
type: 'Padding',
id: 'controlsPadding'
},
{
label: 'Playback area padding',
type: 'Padding',
id: 'playbackPadding'
},
{
label: 'Volume area padding',
type: 'Padding',
id: 'volumePadding'
},
{
label: 'Divider Width',
type: 'Pixels',
id: 'dividerWidth'
},
{
label: 'Divider Color',
type: 'Color',
id: 'dividerColor'
}
]
},
{
label: 'Progress and volume bars',
type: 'Section',
id: 'barsSection',
unlocks: [
{
label: 'Background Color',
type: 'Color',
id: 'barsBackgroundColor'
},
{
label: 'Stream Color',
type: 'Color',
id: 'barsStreamColor'
},
{
label: 'Active Color',
type: 'Color',
id: 'barsActiveColor'
},
{
label: 'Height',
type: 'Pixels',
id: 'barsHeight'
},
{
label: 'Rounded Corners',
type: 'Pixels',
id: 'barsRounded'
}
]
}
],
defaults: {
// Background
backgroundColor: {
type: 'hex',
value: '#ffffff',
opacity: 100
},
padding: '10px',
useBorder: false,
border: false,
useCorners: false,
corners: '0px',
// Layout
controlsPadding: '0px',
playbackPadding: '0px',
volumePadding: '0px',
dividerColor: {
type: 'hex',
value: '#efefef',
opacity: 100
},
// Progress and volume bars
barsBackgroundColor: {
type: 'hex',
value: '#efefef',
opacity: 100
},
barsStreamColor: {
type: 'hex',
value: '#e9e9e9',
opacity: 100
},
barsActiveColor: {
type: 'hex',
value: '#333333',
opacity: 100
},
barsHeight: 6,
barsRounded: 3,
dividerWidth: 1
},
rules: (props) => {
const rules = {
player: {
backgroundColor: getColorString(props.backgroundColor),
borderRadius: props.useCorners && props.corners,
padding: props.padding
},
controls: {
padding: props.controlsPadding
},
playback: {
padding: props.playbackPadding
},
volume: {
padding: props.volumePadding
},
bars: {
backgroundColor: getColorString(props.barsBackgroundColor),
height: props.barsHeight,
borderRadius: props.barsRounded
},
stream: {
backgroundColor: getColorString(props.barsStreamColor)
},
active: {
backgroundColor: getColorString(props.barsActiveColor)
},
divider: {
width: props.dividerWidth,
backgroundColor: getColorString(props.dividerColor)
}
};
if (props.useBorder) {
Utils.applyBorders(rules.player, props.border);
}
return rules;
}
};
================================================
FILE: lib/shared/elements/section/index.jsx
================================================
import cx from 'classnames';
import BackgroundImage from 'components/background-image';
import React, {PropTypes} from 'react';
import propsSchema from './props-schema';
import settings from './settings';
import style from './style';
import Component from '../component';
import Element from '../element';
export default class Section extends Component {
static propTypes = {
useBackgroundImage: PropTypes.bool,
backgroundImage: PropTypes.string,
repeat: PropTypes.string,
vertical: PropTypes.number,
horizontal: PropTypes.number,
navigation: PropTypes.string,
styleClassMap: PropTypes.object,
relax: PropTypes.object.isRequired
};
static defaultProps = {
backgroundImage: '',
repeat: 'no-repeat',
vertical: '50%',
horizontal: '50%',
navigation: ''
};
static propsSchema = propsSchema;
static settings = settings;
static style = style;
render () {
const classMap = this.props.styleClassMap || {};
const props = {
...this.props.relax,
htmlTag: 'div',
style: {
position: 'relative'
},
className: cx(classMap && classMap.section),
settings
};
if (this.props.navigation && this.props.navigation !== '') {
props.id = this.props.navigation;
}
return (
{this.renderBackground()}
{this.renderContent()}
);
}
renderBackground () {
if (this.props.useBackgroundImage) {
return (
);
}
}
}
================================================
FILE: lib/shared/elements/section/props-schema.js
================================================
export default [
{
label: 'Navigation ID',
type: 'String',
id: 'navigation'
},
{
label: 'Background Image',
type: 'Optional',
id: 'useBackgroundImage',
unlocks: [
{
type: 'Image',
id: 'backgroundImage',
unlocks: [
{
label: 'Repeat',
type: 'Select',
id: 'repeat',
props: {
labels: ['No repeat', 'Repeat', 'Repeat horiz.', 'Repeat vert.'],
values: ['no-repeat', 'repeat', 'repeat-x', 'repeat-y']
}
},
{
label: 'Vertical position',
type: 'Percentage',
id: 'vertical'
},
{
label: 'Horizontal position',
type: 'Percentage',
id: 'horizontal'
}
]
}
]
}
];
================================================
FILE: lib/shared/elements/section/settings.js
================================================
export default {
icon: {
class: 'nc-icon-mini design_distribute-vertical'
},
category: 'structure',
drop: {
rejects: 'Section',
customDropArea: true
},
drag: {}
};
================================================
FILE: lib/shared/elements/section/style.js
================================================
import {getColorString, applyBackground} from 'helpers/colors';
export default {
type: 'section',
options: [
{
label: 'Background Color',
type: 'Optional',
id: 'useBackgroundColor',
unlocks: [
{
type: 'Color',
id: 'backgroundColor',
props: {
gradients: true
}
}
]
},
{
label: 'Height',
type: 'Optional',
id: 'useFixHeight',
unlocks: [
{
label: 'Percentage from viewport',
type: 'Percentage',
id: 'heightPerc',
props: {
min: 0,
max: 200
}
},
{
label: 'Content vertical alignment',
type: 'Percentage',
id: 'contentVertical'
}
]
},
{
label: 'Padding',
type: 'Padding',
id: 'padding'
}
],
defaults: {
useBackgroundColor: false,
backgroundColor: {
value: '#ffffff',
opacity: 100
},
useFixHeight: false,
heightPerc: '100%',
contentVertical: '50%',
padding: '20px'
},
rules: (props) => {
const rule = {};
const contentRule = {};
props.useBackgroundColor && applyBackground(rule, props.backgroundColor);
if (props.useFixHeight) {
rule.height = `${parseInt(props.heightPerc, 10)}vh`;
contentRule.position = 'relative';
contentRule.top = props.contentVertical;
contentRule.transform = `translateY(-${props.contentVertical})`;
}
rule.padding = props.padding;
return {
section: rule,
content: contentRule
};
},
getIdentifierLabel: (props) => {
let str = '';
if (props.useFixHeight) {
str += `${props.heightPerc}%`;
} else {
str += 'Auto';
}
str += ' | ';
if (props.useBackgroundColor) {
str += getColorString(props.backgroundColor);
} else {
str += 'transparent';
}
return str;
}
};
================================================
FILE: lib/shared/elements/symbol/classes.js
================================================
import jss from 'helpers/stylesheet';
export default jss.createRules({
row: {
display: 'table',
width: '100%',
tableLayout: 'fixed',
textAlign: 'left'
},
column: {
display: 'table-cell',
verticalAlign: 'top',
position: 'relative'
}
});
================================================
FILE: lib/shared/elements/symbol/container.jsx
================================================
import * as symbolsActions from 'actions/symbols';
import React, {PropTypes} from 'react';
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
import settings from './settings';
import Component from '../component';
import Element from '../element';
@connect(
(state) => ({
symbols: state.symbols
}),
(dispatch) => bindActionCreators(symbolsActions, dispatch)
)
export default class DynamicListContainer extends Component {
static fragments = {
symbol: {
_id: 1,
title: 1,
data: 1
}
};
static propTypes = {
symbols: PropTypes.object.isRequired,
symbolId: PropTypes.string.isRequired,
children: PropTypes.node.isRequired,
relax: PropTypes.object.isRequired
};
getInitState () {
this.fetchSymbol(this.props);
return {};
}
fetchSymbol (props) {
if (props.symbolId &&
(!props.symbols[props.symbolId] || !props.symbols[props.symbolId].data) &&
!this.fething) {
this.fething = true;
props.getSymbol(props.symbolId, this.constructor.fragments);
}
}
render () {
const props = {
htmlTag: 'div',
...this.props.relax,
settings,
className: 'symbol'
};
return (
{this.props.children}
);
}
}
================================================
FILE: lib/shared/elements/symbol/index.jsx
================================================
import React from 'react';
import propsSchema from './props-schema';
import settings from './settings';
import Component from '../component';
import Container from './container';
export default class Symbol extends Component {
static propsSchema = propsSchema;
static settings = settings;
render () {
return ;
}
}
================================================
FILE: lib/shared/elements/symbol/props-schema.js
================================================
export default [];
================================================
FILE: lib/shared/elements/symbol/settings.js
================================================
export default {
icon: {
class: 'nc-icon-mini objects_puzzle-10'
},
drop: false,
drag: {}
};
================================================
FILE: lib/shared/elements/text-box/classes.js
================================================
import jss from 'helpers/stylesheet';
const common = {
fontSize: 'inherit',
lineHeight: 'inherit',
letterSpacing: 'inherit',
fontFamily: 'inherit',
fontStyle: 'inherit',
fontWeight: 'inherit',
margin: 0,
padding: 0,
color: 'inherit'
};
export default jss.createRules({
text: {
outline: 0,
border: 0,
display: 'block',
p: common,
h1: common,
h2: common,
h3: common,
h4: common,
h5: common,
h6: common
},
cursor: {
cursor: 'default'
},
trim: {
'*': {
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
overflow: 'hidden'
},
display: 'inline-block'
}
});
================================================
FILE: lib/shared/elements/text-box/index.jsx
================================================
import cx from 'classnames';
import Editor from 'components/medium-editor';
import React, {PropTypes} from 'react';
import {changeElementContent} from 'actions/page-builder';
import classes from './classes';
import propsSchema from './props-schema';
import settings from './settings';
import style from './style';
import Component from '../component';
import Element from '../element';
export default class TextBox extends Component {
static propTypes = {
usePadding: PropTypes.bool,
padding: PropTypes.string,
useAlign: PropTypes.bool,
textAlign: PropTypes.string,
children: PropTypes.node,
styleClassMap: PropTypes.object,
useTrim: PropTypes.bool,
maxWidth: PropTypes.number,
relax: PropTypes.object.isRequired
};
static defaultProps = {
padding: '0px',
textAlign: 'left',
maxWidth: 200
};
static defaultChildren = 'Click to edit text';
static propsSchema = propsSchema;
static settings = settings;
static style = style;
getStyle () {
const result = {};
if (this.props.usePadding) {
result.padding = this.props.padding;
}
if (this.props.useAlign) {
result.textAlign = this.props.textAlign;
}
return result;
}
onChange (value) {
const {relax} = this.props;
relax.dispatch(changeElementContent(relax.element.id, value));
}
render () {
const props = {
...this.props.relax,
htmlTag: 'div',
settings,
style: this.getStyle()
};
return (
{this.renderContent()}
);
}
renderContent () {
let result;
const classMap = this.props.styleClassMap || {};
const {editing, selected} = this.props.relax;
const styles = {};
const className = cx(classes.text, classMap.text);
let html = '';
if ((!this.props.children || this.props.children === '') && editing && !selected) {
html = 'Double click to edit text';
} else {
html = this.props.children;
}
if (this.props.useTrim) {
styles.maxWidth = this.props.maxWidth;
}
if (editing && selected) {
result = (
);
} else {
result = (
);
}
return result;
}
}
================================================
FILE: lib/shared/elements/text-box/props-schema.js
================================================
export default [
{
label: 'Padding',
type: 'Optional',
id: 'usePadding',
unlocks: [
{
type: 'Padding',
id: 'padding'
}
]
},
{
label: 'Alignment',
type: 'Optional',
id: 'useAlign',
unlocks: [
{
type: 'Select',
id: 'textAlign',
props: {
labels: ['Left', 'Center', 'Right'],
values: ['left', 'center', 'right']
}
}
]
},
{
label: 'Trim',
type: 'Optional',
id: 'useTrim',
unlocks: [
{
type: 'Pixels',
id: 'maxWidth'
}
]
}
];
================================================
FILE: lib/shared/elements/text-box/settings.js
================================================
export default {
icon: {
class: 'nc-icon-mini design_text'
},
category: 'content',
drop: false,
drag: {
dragSelected: false
}
};
================================================
FILE: lib/shared/elements/text-box/style.js
================================================
import utils from 'helpers/utils';
import {getColorString} from 'helpers/colors';
export default {
type: 'text',
options: [
{
label: 'Font Family',
id: 'font',
type: 'Font'
},
{
type: 'Columns',
options: [
{
label: 'Font Size',
id: 'fontSize',
type: 'Number',
props: {
allowed: ['px', 'em', 'pt']
}
},
{
label: 'Line Height',
id: 'lineHeight',
type: 'Pixels'
}
]
},
{
type: 'Columns',
options: [
{
label: 'Letter Spacing',
id: 'letterSpacing',
type: 'Number',
props: {
allowed: ['px', 'em', 'pt']
}
},
{
label: 'Color',
id: 'color',
type: 'Color'
}
]
},
{
label: 'Shadow',
id: 'shadow',
type: 'TextShadow'
},
{
type: 'Columns',
options: [
{
label: 'Links color',
id: 'linkColor',
type: 'Color'
},
{
label: 'Links color hover',
id: 'linkColorOver',
type: 'Color'
}
]
},
{
label: 'Links underline',
id: 'linkUnderline',
type: 'Boolean'
}
],
defaults: {
font: {},
fontSize: '16px',
lineHeight: '16px',
letterSpacing: '0px',
color: {
value: '#ffffff',
opacity: 100
},
shadow: [],
linkUnderline: true,
linkColor: {
value: '#ffffff',
opacity: 100
},
linkColorOver: {
value: '#ffffff',
opacity: 100
}
},
rules: (props) => {
const rule = {
fontSize: props.fontSize,
lineHeight: props.lineHeight,
letterSpacing: props.letterSpacing,
color: getColorString(props.color)
};
if (props.font && props.font.family && props.font.fvd) {
rule.fontFamily = props.font.family;
utils.processFVD(rule, props.font.fvd);
}
if (props.shadow && props.shadow.length > 0) {
utils.applyTextShadows(rule, props.shadow);
}
// links
rule.a = {
textDecoration: props.linkUnderline ? 'underline' : 'none',
color: getColorString(props.linkColor),
':hover': {
color: getColorString(props.linkColorOver)
}
};
return {
text: rule
};
},
getIdentifierLabel: (props) => {
const variation = props.font && props.font.fvd && ` ${props.font.fvd.charAt(1)}00` || '';
return (props.font && props.font.family || '') + variation;
}
};
================================================
FILE: lib/shared/elements/text-input/classes.js
================================================
import jss from 'helpers/stylesheet';
export default jss.createRules({
input: {
display: 'inline-block',
width: '100%',
maxWidth: '100%',
border: '1px solid #cccccc',
outline: 0,
backgroundColor: 'transparent'
},
holder: {
textAlign: 'left'
}
});
================================================
FILE: lib/shared/elements/text-input/index.jsx
================================================
import cx from 'classnames';
import React, {PropTypes} from 'react';
import classes from './classes';
import propsSchema from './props-schema';
import settings from './settings';
import style from './style';
import Component from '../component';
import Element from '../element';
export default class TextInput extends Component {
static propTypes = {
name: PropTypes.string,
placeholder: PropTypes.string,
styleClassMap: PropTypes.object,
relax: PropTypes.object.isRequired
};
static propsSchema = propsSchema;
static settings = settings;
static style = style;
render () {
const classMap = this.props.styleClassMap || {};
const props = {
htmlTag: 'div',
...this.props.relax,
settings,
className: cx(classes.holder, classMap.holder)
};
return (
);
}
}
================================================
FILE: lib/shared/elements/text-input/props-schema.js
================================================
export default [
{
label: 'Name',
type: 'String',
id: 'name'
},
{
label: 'Placeholder',
type: 'String',
id: 'placeholder'
}
];
================================================
FILE: lib/shared/elements/text-input/settings.js
================================================
export default {
icon: {
class: 'nc-icon-mini ui-1_edit-74'
},
category: 'form',
drop: false,
drag: {}
};
================================================
FILE: lib/shared/elements/text-input/style.js
================================================
import Utils from 'helpers/utils';
import {getColorString} from 'helpers/colors';
export default {
type: 'input',
options: [
{
label: 'Background',
type: 'Section',
id: 'backgroundSection',
unlocks: [
{
label: 'Background Color',
type: 'Optional',
id: 'useBackground',
unlocks: [
{
type: 'Color',
id: 'backgroundColor'
}
]
},
{
label: 'Padding',
type: 'Padding',
id: 'padding'
},
{
label: 'Border',
type: 'Optional',
id: 'useBorder',
unlocks: [
{
label: 'Border',
type: 'Border',
id: 'border'
},
{
label: 'Border color on focused',
type: 'Color',
id: 'borderColorFocused'
}
]
},
{
label: 'Rounded Corners',
type: 'Optional',
id: 'useCorners',
unlocks: [
{
type: 'Corners',
id: 'corners'
}
]
}
]
},
{
label: 'Layout',
type: 'Section',
id: 'layoutSection',
unlocks: [
{
label: 'Width',
type: 'Select',
id: 'width',
props: {
labels: ['Full width', 'Max Width'],
values: ['full', 'max']
},
unlocks: {
max: [
{
label: 'Max Width',
id: 'maxWidth',
type: 'Pixels'
},
{
label: 'Align',
id: 'align',
type: 'Select',
props: {
labels: ['Left', 'Center', 'Right'],
values: ['left', 'center', 'right']
}
}
]
}
}
]
},
{
label: 'Text',
type: 'Section',
id: 'textSection',
unlocks: [
{
label: 'Font Family',
id: 'font',
type: 'Font'
},
{
label: 'Font Size',
id: 'fontSize',
type: 'Number',
props: {
allowed: ['px', 'em', 'pt']
}
},
{
label: 'Line Height',
id: 'lineHeight',
type: 'Pixels'
},
{
label: 'Letter Spacing',
id: 'letterSpacing',
type: 'Number',
props: {
allowed: ['px', 'em', 'pt']
}
},
{
label: 'Color',
id: 'color',
type: 'Color'
},
{
label: 'Placeholder Color',
id: 'placeholderColor',
type: 'Color'
},
{
label: 'Text align',
id: 'textAlign',
type: 'Select',
props: {
labels: ['Left', 'Center', 'Right'],
values: ['left', 'center', 'right']
}
}
]
}
],
defaults: {
useBackground: false,
backgroundColor: {
value: '#ffffff',
opacity: 100
},
padding: '10px',
useBorder: false,
border: false,
borderColorFocused: {
value: '#333333',
opacity: 100
},
useCorners: false,
corners: '0px',
width: 'max',
maxWidth: '300px',
align: 'left',
font: {},
fontSize: '16px',
lineHeight: '16px',
letterSpacing: '0px',
color: {
value: '#ffffff',
opacity: 100
},
placeholderColor: {
value: '#ffffff',
opacity: 100
},
textAlign: 'left'
},
rules: (props) => {
const placeholderColor = getColorString(props.placeholderColor);
const style = {
input: {
backgroundColor: props.useBackground && getColorString(props.backgroundColor),
borderRadius: props.useCorners && props.corners,
padding: props.padding,
fontSize: props.fontSize,
lineHeight: props.lineHeight,
color: getColorString(props.color),
letterSpacing: props.letterSpacing,
textAlign: props.textAlign,
':focus': {
borderColor: props.useBorder && getColorString(props.borderColorFocused)
},
'::-webkit-input-placeholder': {
color: placeholderColor
},
':-moz-placeholder': {
color: placeholderColor
},
'::-moz-placeholder': {
color: placeholderColor
},
':-ms-input-placeholder': {
color: placeholderColor
}
},
holder: {
textAlign: props.align
}
};
if (props.useBorder) {
Utils.applyBorders(style.input, props.border);
}
if (props.width === 'max') {
style.input.width = props.maxWidth;
}
if (props.font && props.font.family && props.font.fvd) {
style.input.fontFamily = props.font.family;
Utils.processFVD(style.input, props.font.fvd);
}
return style;
}
};
================================================
FILE: lib/shared/elements/textarea/classes.js
================================================
import jss from 'helpers/stylesheet';
export default jss.createRules({
input: {
display: 'inline-block',
width: '100%',
maxWidth: '100%',
border: '1px solid #cccccc',
outline: 0,
backgroundColor: 'transparent'
},
holder: {
textAlign: 'left'
}
});
================================================
FILE: lib/shared/elements/textarea/index.jsx
================================================
import cx from 'classnames';
import React, {PropTypes} from 'react';
import classes from './classes';
import propsSchema from './props-schema';
import settings from './settings';
import Component from '../component';
import Element from '../element';
export default class Textarea extends Component {
static propTypes = {
name: PropTypes.string,
placeholder: PropTypes.string,
rows: PropTypes.number,
styleClassMap: PropTypes.object,
relax: PropTypes.object.isRequired
};
static defaultProps = {
rows: 6
};
static propsSchema = propsSchema;
static settings = settings;
static style = 'input';
render () {
const classMap = this.props.styleClassMap || {};
const props = {
htmlTag: 'div',
...this.props.relax,
settings,
className: cx(classes.holder, classMap.holder)
};
return (
);
}
}
================================================
FILE: lib/shared/elements/textarea/props-schema.js
================================================
export default [
{
label: 'Name',
type: 'String',
id: 'name'
},
{
label: 'Placeholder',
type: 'String',
id: 'placeholder'
},
{
label: 'Rows',
type: 'Number',
id: 'rows'
}
];
================================================
FILE: lib/shared/elements/textarea/settings.js
================================================
export default {
icon: {
class: 'nc-icon-mini ui-1_edit-76'
},
category: 'form',
drop: false,
drag: {}
};
================================================
FILE: lib/shared/elements/video/index.jsx
================================================
import elementStyles from 'styles/element.less';
import Utils from 'helpers/utils';
import React, {PropTypes} from 'react';
import {findDOMNode} from 'react-dom';
import propsSchema from './props-schema';
import settings from './settings';
import Component from '../component';
import Element from '../element';
export default class Video extends Component {
static propTypes = {
type: PropTypes.string.isRequired,
videoId: PropTypes.string.isRequired,
videoHeight: PropTypes.number.isRequired,
relax: PropTypes.object.isRequired
};
static defaultProps = {
type: 'youtube',
videoId: '',
videoHeight: '56%'
};
static propsSchema = propsSchema;
static settings = settings;
getInitState () {
this.onResizeBind = ::this.onResize;
return {
mounted: false
};
}
componentDidMount () {
window.addEventListener('resize', this.onResizeBind);
this.onResize();
}
componentWillUnmount () {
window.removeEventListener('resize', this.onResizeBind);
}
onResize () {
const dom = findDOMNode(this);
const rect = dom.getBoundingClientRect();
const width = Math.round(rect.right - rect.left);
this.setState({
mounted: true,
width
});
}
render () {
return (
{this.renderIframe()}
);
}
renderIframe () {
let result;
let height = 300;
if (this.state.width) {
height = Math.round(this.state.width * (parseInt(this.props.videoHeight, 10) / 100));
}
if (this.props.videoId && this.props.videoId !== '') {
let src = '';
if (this.props.type === 'youtube') {
const parsedID = Utils.parseYoutubeURL(this.props.videoId);
src = `http://www.youtube.com/embed/${parsedID || this.props.videoId}`;
} else if (this.props.type === 'vimeo') {
const parsedID = Utils.parseVimeoURL(this.props.videoId);
src = `http://player.vimeo.com/video/${parsedID || this.props.videoId}`;
} else if (this.props.type === 'dailymotion') {
const parsedID = Utils.parseDailymotionURL(this.props.videoId);
src = `http://www.dailymotion.com/embed/video/${parsedID || this.props.videoId}`;
}
const iframe = (
);
if (this.props.relax.editing) {
result = (
);
} else {
result = iframe;
}
} else {
const style = {
height
};
result = (
);
}
return result;
}
}
================================================
FILE: lib/shared/elements/video/props-schema.js
================================================
export default [
{
label: 'Video Host',
type: 'Select',
id: 'type',
props: {
labels: ['Youtube', 'Vimeo', 'Dailymotion'],
values: ['youtube', 'vimeo', 'dailymotion']
}
},
{
label: 'Video Id/Url',
type: 'String',
id: 'videoId'
},
{
label: 'Video Height',
type: 'Percentage',
id: 'videoHeight',
props: {
max: 200
}
}
];
================================================
FILE: lib/shared/elements/video/settings.js
================================================
export default {
icon: {
class: 'nc-icon-mini media-1_video-65'
},
category: 'media',
drop: false,
drag: {}
};
================================================
FILE: lib/shared/helpers/colors.js
================================================
import colr from 'colr';
import find from 'lodash.find';
import forEach from 'lodash.foreach';
import sortBy from 'lodash.sortby';
let colorsCollection = [];
export function updateColors (colors) {
colorsCollection = colors;
}
export function hexIsValid (value) {
return /(^#[0-9A-F]{6}$)|(^#[0-9A-F]{3}$)/i.test(value);
}
export function getColor (colorObj, colors = colorsCollection) {
let color = colr().fromHex('#000000');
let opacity = 100;
let label = '#000000';
let type = 'hex';
if (typeof colorObj === 'object') {
opacity = typeof colorObj.opacity === 'number' ? colorObj.opacity : 100;
type = colorObj.type;
switch (colorObj.type) {
case 'palette': {
const colorFromPallete = find(colors, (clr) => (String(clr._id) === colorObj.value));
if (colorFromPallete) {
color = colr().fromHex(colorFromPallete.value);
label = colorFromPallete.label;
}
break;
}
case 'hex':
if (hexIsValid(colorObj.value)) {
color = colr().fromHex(colorObj.value);
label = colorObj.value;
}
break;
case 'hsv':
color = colr().fromHsvObject(colorObj.value);
label = color.toHex();
break;
case 'rgb':
color = colr().fromRgbObject(colorObj.value);
label = color.toHex();
break;
default:
}
}
return {
colr: color,
opacity,
label,
type
};
}
export function getColorString (colorObj, colors = colorsCollection) {
let result = '#000000';
const color = (colorObj && colorObj.colr) ? colorObj : getColor(colorObj, colors);
if (color) {
if (color.opacity === 100) {
result = color.colr.toHex();
} else {
const rgb = color.colr.toRgbObject();
result = `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, ${color.opacity / 100})`;
}
}
return result;
}
export function applyBackground (style, colorObj, colors = colorsCollection) {
if (colorObj.type !== 'linear' && colorObj.type !== 'radial') {
style.backgroundColor = getColorString(colorObj, colors);
} else if (colorObj.type === 'linear') {
const orderedPoints = sortBy(colorObj.points, 'perc');
const gradientColors = [];
forEach(orderedPoints, (point) => {
gradientColors.push(`${getColorString(point, colors)} ${point.perc}%`);
});
style.background = `linear-gradient(${90 - colorObj.angle}deg, ${gradientColors.toString()})`;
// TODO missing other browsers
// `-moz-linear-gradient(${colorObj.angle}deg, ${gradientColors.toString()})`,
// `-webkit-linear-gradient(${colorObj.angle}deg, ${gradientColors.toString()})`,
// `-o-linear-gradient(${colorObj.angle}deg, ${gradientColors.toString()})`,
// `-ms-linear-gradient(${colorObj.angle}deg, ${gradientColors.toString()})`,
} else if (colorObj.type === 'radial') {
const orderedPoints = sortBy(colorObj.points, 'perc');
const gradientColors = [];
forEach(orderedPoints, (point) => {
gradientColors.push(`${getColorString(point, colors)} ${point.perc}%`);
});
let radius;
switch (colorObj.radius) {
case 'cc':
radius = 'closest-corner';
break;
case 'fc':
radius = 'farthest-corner';
break;
case 'cs':
radius = 'closest-side';
break;
case 'fs':
radius = 'farthest-side';
break;
default:
radius = 'farthest-corner';
}
style.background = `radial-gradient(circle ${radius} at ${colorObj.center.left}% ${colorObj.center.top}%, ${gradientColors.toString()})`;
// radial-gradient(circle closest-corner at 25% 50% , #848484, #ededed 100%);
}
}
================================================
FILE: lib/shared/helpers/configure-store.js
================================================
import combineActionsMiddleware from 'redux-combine-actions';
import createLogger from 'redux-logger';
import reducer from 'reducers';
import thunkMiddleware from 'redux-thunk';
import {createStore, applyMiddleware, compose} from 'redux';
const middleware = [];
middleware.push(combineActionsMiddleware);
middleware.push(thunkMiddleware);
if (typeof window !== 'undefined' && module.hot) {
middleware.push(createLogger());
}
export default function configureStore (routerMiddleware, initialState) {
const store = compose(
applyMiddleware(
...middleware
),
routerMiddleware
)(createStore)(reducer, initialState);
if (module.hot) {
// Enable Webpack hot module replacement for reducers
module.hot.accept('../reducers', () => {
const nextReducer = require('../reducers');
store.replaceReducer(nextReducer);
});
}
return store;
}
================================================
FILE: lib/shared/helpers/data-types/index.js
================================================
export const TypesProps = {
String: {
default: ''
},
Boolean: {
default: false
},
Number: {
default: 0,
options: [
{
id: 'min',
label: 'Minimum',
type: 'Number',
props: {
label: '#',
min: false
}
},
{
id: 'max',
label: 'Maximum',
type: 'Number',
props: {
label: '#',
min: false
}
},
{
id: 'label',
label: 'Label',
type: 'String'
}
],
defaults: {
min: -9999,
max: 9999,
label: '#'
}
}
};
export const schemaTypesOrdered = [
'String',
'Html',
'Link',
'Image',
'Video',
'Audio',
'Date',
'Reference',
'Multiple Reference',
'User',
'Select',
'Boolean',
'Number',
'Icon',
'Color'
];
export const dependsOnWhitelist = [
'String',
'Select',
'Boolean',
'Number'
];
export const singleFixedProperties = [
{
id: 'title',
title: 'Title',
type: 'String',
required: true,
locked: true
},
{
id: 'slug',
title: 'Slug',
type: 'String',
required: true,
locked: true
},
{
id: 'state',
title: 'State',
type: 'String',
required: true,
locked: true
},
{
id: 'date',
title: 'Created Date',
type: 'Date',
required: true,
locked: true
},
{
id: 'updatedDate',
title: 'Updated Date',
type: 'Date',
required: true,
locked: true
},
{
id: 'publishedDate',
title: 'Published date',
type: 'Date',
required: true,
locked: true
},
{
id: 'data',
title: 'Page builder data',
type: 'Array',
required: true,
locked: true
}
];
export const propertyOptions = [
{
type: 'Columns',
options: [
{
id: 'title',
label: 'Option Title',
type: 'String'
},
{
id: 'required',
label: 'Option Required',
type: 'Boolean'
}
]
},
{
type: 'Select',
id: 'type',
label: 'Option Type',
props: {
labels: schemaTypesOrdered,
values: schemaTypesOrdered
}
}
];
export default {
TypesProps,
schemaTypesOrdered,
dependsOnWhitelist,
singleFixedProperties,
propertyOptions
};
================================================
FILE: lib/shared/helpers/data-types/native.js
================================================
import {
GraphQLString,
GraphQLInt,
GraphQLBoolean
} from 'graphql';
import {Schema} from 'mongoose';
export const TypesNative = {
String,
Number,
Boolean,
Color: Schema.Types.Mixed,
Font: String,
Html: String,
Icon: String,
Image: String,
Select: String,
Pixels: Number,
Percentage: Number,
Padding: String,
Margin: String,
Corners: String
};
export const TypesNativeGraphQL = {
String: {type: GraphQLString},
Number: {type: GraphQLInt},
Boolean: {type: GraphQLBoolean},
Color: {type: GraphQLString}, // TODO change to color option object
Font: {type: GraphQLString}, // TODO check structure
Html: {type: GraphQLString},
Icon: {type: GraphQLString},
Image: {type: GraphQLString},
Select: {type: GraphQLString},
Pixels: {type: GraphQLInt},
Percentage: {type: GraphQLInt},
Padding: {type: GraphQLString},
Margin: {type: GraphQLString},
Corners: {type: GraphQLString}
};
export default {
TypesNative,
TypesNativeGraphQL
};
================================================
FILE: lib/shared/helpers/displays.js
================================================
export default {
desktop: 99999,
tablet: 911,
mobile: 479
};
================================================
FILE: lib/shared/helpers/ga-send.js
================================================
export default function gaSend () {
if (typeof window !== 'undefined') {
window.ga && window.ga('send', 'pageview');
}
}
================================================
FILE: lib/shared/helpers/get-element-position.js
================================================
import forEach from 'lodash.foreach';
import displays from './displays';
export default function getElementPosition (element, display) {
let elementPosition = element.position;
if (display !== 'desktop' && element.displayPosition) {
const changes = {};
forEach(displays, (value, displayIt) => {
if (displayIt !== 'desktop' && value >= displays[display] && element.displayProps[displayIt]) {
Object.assign(changes, element.displayPosition[displayIt]);
}
});
elementPosition = Object.assign({}, elementPosition, changes);
}
return elementPosition;
}
================================================
FILE: lib/shared/helpers/get-element-props.js
================================================
import forEach from 'lodash.foreach';
import displays from './displays';
export default function getElementProps (element, display) {
let elementProps = element.props;
if (display !== 'desktop' && element.displayProps) {
const changes = {};
forEach(displays, (value, displayIt) => {
if (displayIt !== 'desktop' && value >= displays[display] && element.displayProps[displayIt]) {
Object.assign(changes, element.displayProps[displayIt]);
}
});
elementProps = Object.assign({}, elementProps, changes);
}
return elementProps;
}
================================================
FILE: lib/shared/helpers/get-element-style-values.js
================================================
import forEach from 'lodash.foreach';
import displays from './displays';
export default function getElementStyleValues (defaults, options, displayOptions, display) {
const displayValues = {};
if (display !== 'desktop' && displayOptions) {
forEach(displays, (value, displayIt) => {
if (displayIt !== 'desktop' && value >= displays[display] && displayOptions[displayIt]) {
Object.assign(displayValues, displayOptions[displayIt]);
}
});
}
return Object.assign({}, defaults, options, displayValues);
}
================================================
FILE: lib/shared/helpers/get-gravatar-image.js
================================================
import md5 from 'js-md5';
export default (email, size = 100) => {
const hash = email ? md5(email.toLowerCase()) : '0';
return `http://www.gravatar.com/avatar/${hash}?d=mm&s=${size}`;
};
================================================
FILE: lib/shared/helpers/icons.js
================================================
export default [
{
family: 'FontAwesome',
baseClass: 'fa',
reference: 'className',
link: 'http://fortawesome.github.io/Font-Awesome/',
icons: [
// web application icons
'fa-adjust',
'fa-adn',
'fa-align-center',
'fa-align-justify',
'fa-align-left',
'fa-align-right',
'fa-ambulance',
'fa-anchor',
'fa-android',
'fa-angellist',
'fa-angle-double-down',
'fa-angle-double-left',
'fa-angle-double-right',
'fa-angle-double-up',
'fa-apple',
'fa-archive',
'fa-area-chart',
'fa-arrow-circle-down',
'fa-arrow-circle-left',
'fa-arrow-circle-o-down',
'fa-arrow-circle-up',
'fa-arrow-down',
'fa-arrow-left',
'fa-arrow-right',
'fa-arrow-up',
'fa-arrows',
'fa-arrows-alt',
'fa-arrows-h',
'fa-arrows-v',
'fa-asterisk',
'fa-at',
'fa-automobile',
'fa-backward',
'fa-ban',
'fa-bank',
'fa-bar-chart',
'fa-barcode',
'fa-bars',
'fa-bed',
'fa-beer',
'fa-behance',
'fa-behance-square',
'fa-bell',
'fa-bell-o',
'fa-bell-slash',
'fa-bell-slash-o',
'fa-bicycle',
'fa-binoculars',
'fa-birthday-cake',
'fa-bitbucket',
'fa-bitbucket-square',
'fa-bitcoin',
'fa-bold',
'fa-bolt',
'fa-bomb',
'fa-book',
'fa-bookmark',
'fa-bookmark-o',
'fa-briefcase',
'fa-bug',
'fa-building',
'fa-building-o',
'fa-bullhorn',
'fa-bullseye',
'fa-bus',
'fa-buysellads',
'fa-cab',
'fa-calculator',
'fa-calendar',
'fa-calendar-o',
'fa-camera',
'fa-camera-retro',
'fa-car',
'fa-caret-down',
'fa-caret-left',
'fa-caret-right',
'fa-caret-square-o-down',
'fa-caret-square-o-left',
'fa-caret-square-o-right',
'fa-caret-square-o-up',
'fa-caret-up',
'fa-cart-arrow-down',
'fa-cart-plus',
'fa-cc',
'fa-cc-amex',
'fa-cc-discover',
'fa-cc-mastercard',
'fa-cc-paypal',
'fa-cc-stripe',
'fa-cc-visa',
'fa-certificate',
'fa-chain',
'fa-chain-broken',
'fa-check',
'fa-check-circle',
'fa-check-circle-o',
'fa-check-square',
'fa-check-square-o',
'fa-chevron-circle-down',
'fa-chevron-circle-left',
'fa-chevron-circle-right',
'fa-chevron-circle-up',
'fa-chevron-down',
'fa-chevron-left',
'fa-chevron-right',
'fa-chevron-up',
'fa-child',
'fa-circle',
'fa-circle-o',
'fa-circle-o-notch',
'fa-circle-thin',
'fa-clipboard',
'fa-clock-o',
'fa-close',
'fa-cloud',
'fa-cloud-download',
'fa-cloud-upload',
'fa-cny',
'fa-code',
'fa-code-fork',
'fa-codepen',
'fa-coffee',
'fa-cog',
'fa-cogs',
'fa-columns',
'fa-comment',
'fa-comment-o',
'fa-comments',
'fa-comments-o',
'fa-compass',
'fa-compress',
'fa-connectdevelop',
'fa-copy',
'fa-copyright',
'fa-credit-card',
'fa-crop',
'fa-crosshairs',
'fa-css3',
'fa-cube',
'fa-cubes',
'fa-cut',
'fa-cutlery',
'fa-dashboard',
'fa-dashcube',
'fa-database',
'fa-delicious',
'fa-desktop',
'fa-deviantart',
'fa-diamond',
'fa-digg',
'fa-dollar',
'fa-dot-circle-o',
'fa-download',
'fa-dribbble',
'fa-dropbox',
'fa-drupal',
'fa-edit',
'fa-eject',
'fa-ellipsis-h',
'fa-ellipsis-v',
'fa-empire',
'fa-envelope',
'fa-envelope-o',
'fa-envelope-square',
'fa-eraser',
'fa-euro',
'fa-exchange',
'fa-exclamation',
'fa-exclamation-circle',
'fa-exclamation-triangle',
'fa-expand',
'fa-external-link',
'fa-external-link-square',
'fa-eye',
'fa-eye-slash',
'fa-eyedropper',
'fa-facebook',
'fa-facebook-official',
'fa-facebook-square',
'fa-fast-backward',
'fa-fast-forward',
'fa-fax',
'fa-female',
'fa-fighter-jet',
'fa-file',
'fa-file-archive-o',
'fa-file-audio-o',
'fa-file-code-o',
'fa-file-excel-o',
'fa-file-image-o',
'fa-file-movie-o',
'fa-file-pdf-o',
'fa-file-photo-o',
'fa-file-picture-o',
'fa-file-powerpoint-o',
'fa-file-sound-o',
'fa-file-text',
'fa-file-text-o',
'fa-file-video-o',
'fa-file-word-o',
'fa-file-zip-o',
'fa-files-o',
'fa-film',
'fa-filter',
'fa-fire',
'fa-fire-extinguisher',
'fa-flag',
'fa-flag-checkered',
'fa-flag-o',
'fa-flash',
'fa-flask',
'fa-flickr',
'fa-floppy-o',
'fa-folder',
'fa-folder-o',
'fa-folder-open',
'fa-folder-open-o',
'fa-font',
'fa-forumbee',
'fa-forward',
'fa-foursquare',
'fa-frown-o',
'fa-futbol-o',
'fa-gamepad',
'fa-gavel',
'fa-gbp',
'fa-gear',
'fa-gears',
'fa-genderless',
'fa-gift',
'fa-git',
'fa-git-square',
'fa-github',
'fa-github-alt',
'fa-github-square',
'fa-gittip',
'fa-glass',
'fa-globe',
'fa-google',
'fa-google-plus',
'fa-google-plus-square',
'fa-google-wallet',
'fa-graduation-cap',
'fa-gratipay',
'fa-group',
'fa-h-square',
'fa-hacker-news',
'fa-hand-o-down',
'fa-hand-o-left',
'fa-hand-o-right',
'fa-hand-o-up',
'fa-hdd-o',
'fa-header',
'fa-headphones',
'fa-heart',
'fa-heart-o',
'fa-heartbeat',
'fa-history',
'fa-home',
'fa-hospital-o',
'fa-hotel',
'fa-html5',
'fa-ils',
'fa-image',
'fa-inbox',
'fa-indent',
'fa-info',
'fa-info-circle',
'fa-inr',
'fa-instagram',
'fa-institution',
'fa-ioxhost',
'fa-italic',
'fa-joomla',
'fa-jpy',
'fa-jsfiddle',
'fa-key',
'fa-keyboard-o',
'fa-krw',
'fa-language',
'fa-laptop',
'fa-lastfm',
'fa-lastfm-square',
'fa-leaf',
'fa-leanpub',
'fa-legal',
'fa-lemon-o',
'fa-level-down',
'fa-level-up',
'fa-life-bouy',
'fa-lightbulb-o',
'fa-line-chart',
'fa-link',
'fa-linkedin',
'fa-linkedin-square',
'fa-linux',
'fa-list',
'fa-list-alt',
'fa-list-ol',
'fa-list-ul',
'fa-location-arrow',
'fa-lock',
'fa-long-arrow-down',
'fa-long-arrow-left',
'fa-long-arrow-right',
'fa-long-arrow-up',
'fa-magic',
'fa-magnet',
'fa-mail-forward',
'fa-mail-reply',
'fa-mail-reply-all',
'fa-male',
'fa-map-marker',
'fa-mars',
'fa-mars-double',
'fa-mars-stroke',
'fa-mars-stroke-h',
'fa-mars-stroke-v',
'fa-maxcdn',
'fa-meanpath',
'fa-medium',
'fa-medkit',
'fa-meh-o',
'fa-mercury',
'fa-microphone',
'fa-microphone-slash',
'fa-minus',
'fa-minus-circle',
'fa-minus-square',
'fa-minus-square-o',
'fa-mobile',
'fa-money',
'fa-moon-o',
'fa-motorcycle',
'fa-music',
'fa-neuter',
'fa-newspaper-o',
'fa-openid',
'fa-outdent',
'fa-pagelines',
'fa-paint-brush',
'fa-paper-plane',
'fa-paper-plane-o',
'fa-paperclip',
'fa-paragraph',
'fa-paste',
'fa-pause',
'fa-paw',
'fa-paypal',
'fa-pencil',
'fa-pencil-square',
'fa-pencil-square-o',
'fa-phone',
'fa-phone-square',
'fa-picture-o',
'fa-pie-chart',
'fa-pied-piper',
'fa-pied-piper-alt',
'fa-pinterest',
'fa-pinterest-p',
'fa-pinterest-square',
'fa-plane',
'fa-play',
'fa-play-circle',
'fa-play-circle-o',
'fa-plug',
'fa-plus',
'fa-plus-circle',
'fa-plus-square',
'fa-plus-square-o',
'fa-power-off',
'fa-print',
'fa-puzzle-piece',
'fa-qq',
'fa-qrcode',
'fa-question',
'fa-question-circle',
'fa-quote-left',
'fa-quote-right',
'fa-random',
'fa-rebel',
'fa-recycle',
'fa-reddit',
'fa-reddit-square',
'fa-refresh',
'fa-remove',
'fa-renren',
'fa-repeat',
'fa-reply',
'fa-reply-all',
'fa-retweet',
'fa-road',
'fa-rocket',
'fa-rouble',
'fa-rss',
'fa-rss-square',
'fa-rub',
'fa-ruble',
'fa-rupee',
'fa-save',
'fa-scissors',
'fa-search',
'fa-search-minus',
'fa-search-plus',
'fa-sellsy',
'fa-server',
'fa-share',
'fa-share-alt',
'fa-share-alt-square',
'fa-share-square',
'fa-share-square-o',
'fa-shekel',
'fa-shield',
'fa-ship',
'fa-shirtsinbulk',
'fa-shopping-cart',
'fa-sign-in',
'fa-sign-out',
'fa-signal',
'fa-simplybuilt',
'fa-sitemap',
'fa-skyatlas',
'fa-skype',
'fa-slack',
'fa-sliders',
'fa-slideshare',
'fa-smile-o',
'fa-sort',
'fa-sort-alpha-asc',
'fa-sort-alpha-desc',
'fa-sort-amount-asc',
'fa-sort-amount-desc',
'fa-sort-asc',
'fa-sort-desc',
'fa-sort-numeric-asc',
'fa-sort-numeric-desc',
'fa-soundcloud',
'fa-space-shuttle',
'fa-spinner',
'fa-spoon',
'fa-spotify',
'fa-square',
'fa-square-o',
'fa-stack-exchange',
'fa-stack-overflow',
'fa-star',
'fa-star-half',
'fa-star-half-o',
'fa-star-o',
'fa-steam',
'fa-steam-square',
'fa-step-backward',
'fa-step-forward',
'fa-stethoscope',
'fa-stop',
'fa-street-view',
'fa-strikethrough',
'fa-stumbleupon',
'fa-stumbleupon-circle',
'fa-subscript',
'fa-subway',
'fa-suitcase',
'fa-sun-o',
'fa-superscript',
'fa-support',
'fa-table',
'fa-tablet',
'fa-tachometer',
'fa-tag',
'fa-tags',
'fa-tasks',
'fa-taxi',
'fa-tencent-weibo',
'fa-terminal',
'fa-text-height',
'fa-text-width',
'fa-th',
'fa-th-large',
'fa-th-list',
'fa-thumb-tack',
'fa-thumbs-down',
'fa-thumbs-o-down',
'fa-thumbs-o-up',
'fa-thumbs-up',
'fa-ticket',
'fa-times',
'fa-times-circle',
'fa-times-circle-o',
'fa-tint',
'fa-toggle-off',
'fa-toggle-on',
'fa-train',
'fa-transgender',
'fa-transgender-alt',
'fa-trash',
'fa-trash-o',
'fa-tree',
'fa-trello',
'fa-trophy',
'fa-truck',
'fa-try',
'fa-tty',
'fa-tumblr',
'fa-tumblr-square',
'fa-turkish-lira',
'fa-twitch',
'fa-twitter',
'fa-twitter-square',
'fa-umbrella',
'fa-underline',
'fa-undo',
'fa-university',
'fa-unlock',
'fa-unlock-alt',
'fa-unsorted',
'fa-upload',
'fa-usd',
'fa-user',
'fa-user-md',
'fa-user-plus',
'fa-user-secret',
'fa-user-times',
'fa-users',
'fa-venus',
'fa-venus-double',
'fa-venus-mars',
'fa-viacoin',
'fa-video-camera',
'fa-vimeo-square',
'fa-vine',
'fa-vk',
'fa-volume-down',
'fa-volume-off',
'fa-volume-up',
'fa-warning',
'fa-wechat',
'fa-weibo',
'fa-weixin',
'fa-whatsapp',
'fa-wheelchair',
'fa-wifi',
'fa-windows',
'fa-won',
'fa-wordpress',
'fa-wrench',
'fa-xing',
'fa-xing-square',
'fa-yahoo',
'fa-yelp',
'fa-youtube',
'fa-youtube-play',
'fa-youtube-square'
]
},
{
family: 'Google icons',
baseClass: 'material-icons',
reference: 'content',
link: 'https://www.google.com/design/icons/',
icons: [
'3d_rotation',
'access_alarm',
'access_alarms',
'access_time',
'accessibility',
'account_balance',
'account_balance_wallet',
'account_box',
'account_circle',
'adb',
'add',
'add_alarm',
'add_alert',
'add_box',
'add_circle',
'add_circle_outline',
'add_shopping_cart',
'add_to_photos',
'adjust',
'airline_seat_flat',
'airline_seat_flat_angled',
'airline_seat_individual_suite',
'airline_seat_legroom_extra',
'airline_seat_legroom_normal',
'airline_seat_legroom_reduced',
'airline_seat_recline_extra',
'airline_seat_recline_normal',
'airplanemode_active',
'airplanemode_inactive',
'airplay',
'alarm',
'alarm_add',
'alarm_off',
'alarm_on',
'album',
'android',
'announcement',
'apps',
'archive',
'arrow_back',
'arrow_drop_down',
'arrow_drop_down_circle',
'arrow_drop_up',
'arrow_forward',
'aspect_ratio',
'assessment',
'assignment',
'assignment_ind',
'assignment_late',
'assignment_return',
'assignment_returned',
'assignment_turned_in',
'assistant',
'assistant_photo',
'attach_file',
'attach_money',
'attachment',
'audiotrack',
'autorenew',
'av_timer',
'backspace',
'backup',
'battery_alert',
'battery_charging_full',
'battery_full',
'battery_std',
'battery_unknown',
'beenhere',
'block',
'bluetooth',
'bluetooth_audio',
'bluetooth_connected',
'bluetooth_disabled',
'bluetooth_searching',
'blur_circular',
'blur_linear',
'blur_off',
'blur_on',
'book',
'bookmark',
'bookmark_border',
'border_all',
'border_bottom',
'border_clear',
'border_color',
'border_horizontal',
'border_inner',
'border_left',
'border_outer',
'border_right',
'border_style',
'border_top',
'border_vertical',
'brightness_1',
'brightness_2',
'brightness_3',
'brightness_4',
'brightness_5',
'brightness_6',
'brightness_7',
'brightness_auto',
'brightness_high',
'brightness_low',
'brightness_medium',
'broken_image',
'brush',
'bug_report',
'build',
'business',
'cached',
'cake',
'call',
'call_end',
'call_made',
'call_merge',
'call_missed',
'call_received',
'call_split',
'camera',
'camera_alt',
'camera_enhance',
'camera_front',
'camera_rear',
'camera_roll',
'cancel',
'card_giftcard',
'card_membership',
'card_travel',
'cast',
'cast_connected',
'center_focus_strong',
'center_focus_weak',
'change_history',
'chat',
'chat_bubble',
'chat_bubble_outline',
'check',
'check_box',
'check_box_outline_blank',
'check_circle',
'chevron_left',
'chevron_right',
'chrome_reader_mode',
'class',
'clear',
'clear_all',
'close',
'closed_caption',
'cloud',
'cloud_circle',
'cloud_done',
'cloud_download',
'cloud_off',
'cloud_queue',
'cloud_upload',
'code',
'collections',
'collections_bookmark',
'color_lens',
'colorize',
'comment',
'compare',
'computer',
'confirmation_number',
'contact_phone',
'contacts',
'content_copy',
'content_cut',
'content_paste',
'control_point',
'control_point_duplicate',
'create',
'credit_card',
'crop',
'crop_16_9',
'crop_3_2',
'crop_5_4',
'crop_7_5',
'crop_din',
'crop_free',
'crop_landscape',
'crop_original',
'crop_portrait',
'crop_square',
'dashboard',
'data_usage',
'dehaze',
'delete',
'description',
'desktop_mac',
'desktop_windows',
'details',
'developer_board',
'developer_mode',
'device_hub',
'devices',
'dialer_sip',
'dialpad',
'directions',
'directions_bike',
'directions_boat',
'directions_bus',
'directions_car',
'directions_railway',
'directions_run',
'directions_subway',
'directions_transit',
'directions_walk',
'disc_full',
'dns',
'do_not_disturb',
'do_not_disturb_alt',
'dock',
'domain',
'done',
'done_all',
'drafts',
'drive_eta',
'dvr',
'edit',
'eject',
'email',
'equalizer',
'error',
'error_outline',
'event',
'event_available',
'event_busy',
'event_note',
'event_seat',
'exit_to_app',
'expand_less',
'expand_more',
'explicit',
'explore',
'exposure',
'exposure_neg_1',
'exposure_neg_2',
'exposure_plus_1',
'exposure_plus_2',
'exposure_zero',
'extension',
'face',
'fast_forward',
'fast_rewind',
'favorite',
'favorite_border',
'feedback',
'file_download',
'file_upload',
'filter',
'filter_1',
'filter_2',
'filter_3',
'filter_4',
'filter_5',
'filter_6',
'filter_7',
'filter_8',
'filter_9',
'filter_9_plus',
'filter_b_and_w',
'filter_center_focus',
'filter_drama',
'filter_frames',
'filter_hdr',
'filter_list',
'filter_none',
'filter_tilt_shift',
'filter_vintage',
'find_in_page',
'find_replace',
'flag',
'flare',
'flash_auto',
'flash_off',
'flash_on',
'flight',
'flight_land',
'flight_takeoff',
'flip',
'flip_to_back',
'flip_to_front',
'folder',
'folder_open',
'folder_shared',
'folder_special',
'font_download',
'format_align_center',
'format_align_justify',
'format_align_left',
'format_align_right',
'format_bold',
'format_clear',
'format_color_fill',
'format_color_reset',
'format_color_text',
'format_indent_decrease',
'format_indent_increase',
'format_italic',
'format_line_spacing',
'format_list_bulleted',
'format_list_numbered',
'format_paint',
'format_quote',
'format_size',
'format_strikethrough',
'format_textdirection_l_to_r',
'format_textdirection_r_to_l',
'format_underlined',
'forum',
'forward',
'forward_10',
'forward_30',
'forward_5',
'fullscreen',
'fullscreen_exit',
'functions',
'gamepad',
'games',
'gesture',
'get_app',
'gif',
'gps_fixed',
'gps_not_fixed',
'gps_off',
'grade',
'gradient',
'grain',
'graphic_eq',
'grid_off',
'grid_on',
'group',
'group_add',
'group_work',
'hd',
'hdr_off',
'hdr_on',
'hdr_strong',
'hdr_weak',
'headset',
'headset_mic',
'healing',
'hearing',
'help',
'help_outline',
'high_quality',
'highlight_off',
'history',
'home',
'hotel',
'hourglass_empty',
'hourglass_full',
'http',
'https',
'image',
'image_aspect_ratio',
'import_export',
'inbox',
'indeterminate_check_box',
'info',
'info_outline',
'input',
'insert_chart',
'insert_comment',
'insert_drive_file',
'insert_emoticon',
'insert_invitation',
'insert_link',
'insert_photo',
'invert_colors',
'invert_colors_off',
'iso',
'keyboard',
'keyboard_arrow_down',
'keyboard_arrow_left',
'keyboard_arrow_right',
'keyboard_arrow_up',
'keyboard_backspace',
'keyboard_capslock',
'keyboard_hide',
'keyboard_return',
'keyboard_tab',
'keyboard_voice',
'label',
'label_outline',
'landscape',
'language',
'laptop',
'laptop_chromebook',
'laptop_mac',
'laptop_windows',
'launch',
'layers',
'layers_clear',
'leak_add',
'leak_remove',
'lens',
'library_add',
'library_books',
'library_music',
'link',
'list',
'live_help',
'live_tv',
'local_activity',
'local_airport',
'local_atm',
'local_bar',
'local_cafe',
'local_car_wash',
'local_convenience_store',
'local_dining',
'local_drink',
'local_florist',
'local_gas_station',
'local_grocery_store',
'local_hospital',
'local_hotel',
'local_laundry_service',
'local_library',
'local_mall',
'local_movies',
'local_offer',
'local_parking',
'local_pharmacy',
'local_phone',
'local_pizza',
'local_play',
'local_post_office',
'local_printshop',
'local_see',
'local_shipping',
'local_taxi',
'location_city',
'location_disabled',
'location_off',
'location_on',
'location_searching',
'lock',
'lock_open',
'lock_outline',
'looks',
'looks_3',
'looks_4',
'looks_5',
'looks_6',
'looks_one',
'looks_two',
'loop',
'loupe',
'loyalty',
'mail',
'map',
'markunread',
'markunread_mailbox',
'memory',
'menu',
'merge_type',
'message',
'mic',
'mic_none',
'mic_off',
'mms',
'mode_comment',
'mode_edit',
'money_off',
'monochrome_photos',
'mood',
'mood_bad',
'more',
'more_horiz',
'more_vert',
'mouse',
'movie',
'movie_creation',
'music_note',
'my_location',
'nature',
'nature_people',
'navigate_before',
'navigate_next',
'navigation',
'network_cell',
'network_locked',
'network_wifi',
'new_releases',
'nfc',
'no_sim',
'not_interested',
'note_add',
'notifications',
'notifications_active',
'notifications_none',
'notifications_off',
'notifications_paused',
'offline_pin',
'ondemand_video',
'open_in_browser',
'open_in_new',
'open_with',
'pages',
'pageview',
'palette',
'panorama',
'panorama_fish_eye',
'panorama_horizontal',
'panorama_vertical',
'panorama_wide_angle',
'party_mode',
'pause',
'pause_circle_filled',
'pause_circle_outline',
'payment',
'people',
'people_outline',
'perm_camera_mic',
'perm_contact_calendar',
'perm_data_setting',
'perm_device_information',
'perm_identity',
'perm_media',
'perm_phone_msg',
'perm_scan_wifi',
'person',
'person_add',
'person_outline',
'person_pin',
'personal_video',
'phone',
'phone_android',
'phone_bluetooth_speaker',
'phone_forwarded',
'phone_in_talk',
'phone_iphone',
'phone_locked',
'phone_missed',
'phone_paused',
'phonelink',
'phonelink_erase',
'phonelink_lock',
'phonelink_off',
'phonelink_ring',
'phonelink_setup',
'photo',
'photo_album',
'photo_camera',
'photo_library',
'photo_size_select_actual',
'photo_size_select_large',
'photo_size_select_small',
'picture_as_pdf',
'picture_in_picture',
'pin_drop',
'place',
'play_arrow',
'play_circle_filled',
'play_circle_outline',
'play_for_work',
'playlist_add',
'plus_one',
'poll',
'polymer',
'portable_wifi_off',
'portrait',
'power',
'power_input',
'power_settings_new',
'present_to_all',
'print',
'public',
'publish',
'query_builder',
'question_answer',
'queue',
'queue_music',
'radio',
'radio_button_checked',
'radio_button_unchecked',
'rate_review',
'receipt',
'recent_actors',
'redeem',
'redo',
'refresh',
'remove',
'remove_circle',
'remove_circle_outline',
'remove_red_eye',
'reorder',
'repeat',
'repeat_one',
'replay',
'replay_10',
'replay_30',
'replay_5',
'reply',
'reply_all',
'report',
'report_problem',
'restaurant_menu',
'restore',
'ring_volume',
'room',
'rotate_90_degrees_ccw',
'rotate_left',
'rotate_right',
'router',
'satellite',
'save',
'scanner',
'schedule',
'school',
'screen_lock_landscape',
'screen_lock_portrait',
'screen_lock_rotation',
'screen_rotation',
'sd_card',
'sd_storage',
'search',
'security',
'select_all',
'send',
'settings',
'settings_applications',
'settings_backup_restore',
'settings_bluetooth',
'settings_brightness',
'settings_cell',
'settings_ethernet',
'settings_input_antenna',
'settings_input_component',
'settings_input_composite',
'settings_input_hdmi',
'settings_input_svideo',
'settings_overscan',
'settings_phone',
'settings_power',
'settings_remote',
'settings_system_daydream',
'settings_voice',
'share',
'shop',
'shop_two',
'shopping_basket',
'shopping_cart',
'shuffle',
'signal_cellular_4_bar',
'signal_cellular_connected_no_internet_4_bar',
'signal_cellular_no_sim',
'signal_cellular_null',
'signal_cellular_off',
'signal_wifi_4_bar',
'signal_wifi_4_bar_lock',
'signal_wifi_off',
'sim_card',
'sim_card_alert',
'skip_next',
'skip_previous',
'slideshow',
'smartphone',
'sms',
'sms_failed',
'snooze',
'sort',
'sort_by_alpha',
'space_bar',
'speaker',
'speaker_group',
'speaker_notes',
'speaker_phone',
'spellcheck',
'star',
'star_border',
'star_half',
'stars',
'stay_current_landscape',
'stay_current_portrait',
'stay_primary_landscape',
'stay_primary_portrait',
'stop',
'storage',
'store',
'store_mall_directory',
'straighten',
'strikethrough_s',
'style',
'subject',
'subtitles',
'supervisor_account',
'surround_sound',
'swap_calls',
'swap_horiz',
'swap_vert',
'swap_vertical_circle',
'switch_camera',
'switch_video',
'sync',
'sync_disabled',
'sync_problem',
'system_update',
'system_update_alt',
'tab',
'tab_unselected',
'tablet',
'tablet_android',
'tablet_mac',
'tag_faces',
'tap_and_play',
'terrain',
'text_format',
'textsms',
'texture',
'theaters',
'thumb_down',
'thumb_up',
'thumbs_up_down',
'time_to_leave',
'timelapse',
'timer',
'timer_10',
'timer_3',
'timer_off',
'toc',
'today',
'toll',
'tonality',
'toys',
'track_changes',
'traffic',
'transform',
'translate',
'trending_down',
'trending_flat',
'trending_up',
'tune',
'turned_in',
'turned_in_not',
'tv',
'undo',
'unfold_less',
'unfold_more',
'usb',
'verified_user',
'vertical_align_bottom',
'vertical_align_center',
'vertical_align_top',
'vibration',
'video_library',
'videocam',
'videocam_off',
'view_agenda',
'view_array',
'view_carousel',
'view_column',
'view_comfy',
'view_compact',
'view_day',
'view_headline',
'view_list',
'view_module',
'view_quilt',
'view_stream',
'view_week',
'vignette',
'visibility',
'visibility_off',
'voice_chat',
'voicemail',
'volume_down',
'volume_mute',
'volume_off',
'volume_up',
'vpn_key',
'vpn_lock',
'wallpaper',
'warning',
'watch',
'wb_auto',
'wb_cloudy',
'wb_incandescent',
'wb_iridescent',
'wb_sunny',
'wc',
'web',
'whatshot',
'widgets',
'wifi',
'wifi_lock',
'wifi_tethering',
'work',
'wrap_text',
'youtube_searched_for',
'zoom_in',
'zoom_out'
]
}
];
================================================
FILE: lib/shared/helpers/load-fonts.js
================================================
import Q from 'q';
export default function loadFonts (body) {
const {dispatch: callback, webfontloader, type} = body;
return new Q()
.then(() => {
const deferred = Q.defer();
let promise = deferred.promise;
const newFonts = {};
function loadingFontsFinished () {
deferred.resolve(newFonts);
}
function fontActive (familyName, fvd) {
if (!newFonts[familyName]) {
newFonts[familyName] = [];
}
newFonts[familyName].push(fvd);
}
if (Object.keys(webfontloader).length === 0) {
deferred.resolve(newFonts);
} else {
const params = {
active: loadingFontsFinished,
fontactive: fontActive,
...webfontloader
};
WebFont.load(params); // eslint-disable-line no-undef
}
if (callback) {
promise = promise.then((fonts) => {
callback({type, fonts});
});
}
return promise;
});
}
================================================
FILE: lib/shared/helpers/mime-types.js
================================================
const IMAGE_MIME_TYPES = [
'image/jpeg',
'image/png',
'image/webp',
'image/tiff',
'image/gif',
'image/vnd.microsoft.icon',
'image/x-icon'
];
const ICON_MIME_TYPES = ['image/vnd.microsoft.icon', 'image/x-icon'];
const VIDEO_MIME_TYPES = ['video/mp4', 'video/webm', 'video/ogg'];
const AUDIO_MIME_TYPES = ['audio/mp3', 'audio/mpeg', 'audio/ogg', 'audio/wav'];
export function getMediaType (mimeType) {
let result = '';
if (ICON_MIME_TYPES.indexOf(mimeType) !== -1) {
result = 'favicon';
} else if (IMAGE_MIME_TYPES.indexOf(mimeType) !== -1) {
result = 'image';
} else if (VIDEO_MIME_TYPES.indexOf(mimeType) !== -1) {
result = 'video';
} else if (AUDIO_MIME_TYPES.indexOf(mimeType) !== -1) {
result = 'audio';
}
return result;
}
export function getMimeTypes (type) {
switch (type) {
case 'favicon':
return ICON_MIME_TYPES;
case 'image':
return IMAGE_MIME_TYPES;
case 'video':
return VIDEO_MIME_TYPES;
case 'audio':
return AUDIO_MIME_TYPES;
default:
return false;
}
}
================================================
FILE: lib/shared/helpers/parse-fields.js
================================================
import forEach from 'lodash.foreach';
export default function parseFields (data, keys) {
let result = data;
if (data) {
result = Object.assign({}, data);
forEach(keys, (key) => {
if (result[key] && typeof result[key] === 'string') {
result[key] = JSON.parse(result[key]);
}
});
}
return result;
}
================================================
FILE: lib/shared/helpers/parse-settings.js
================================================
import forEach from 'lodash.foreach';
export default function parseSettings (_settings) {
const settings = {};
forEach(_settings, (setting) => {
settings[setting._id] = setting.value;
});
return settings;
}
================================================
FILE: lib/shared/helpers/request.js
================================================
import request from 'superagent';
import Q from 'q';
export default function doRequest (body) {
const {dispatch: callback, query, variables, type, params} = body;
return new Q()
.then(() => {
const deferred = Q.defer();
let promise = deferred.promise;
const req = request
.post('/graphql')
.set('Content-Type', 'application/json')
.set('Accept', 'application/json')
.send({query, variables});
req
.end((error, res) => {
if (error) {
deferred.reject(error);
} else {
deferred.resolve(res.body);
}
});
if (callback) {
promise = promise.then(({data, errors}) => {
callback({type, data, errors, params});
return data;
});
}
return promise;
});
}
================================================
FILE: lib/shared/helpers/schema-filter-default-options.js
================================================
export default function getFilterDefaultOptions (type) {
switch (type) {
case 'Date':
return {
fromGran: 'day',
fromValue: 1,
toGran: 'present',
toValue: 1
};
case 'String':
return {
op: 'equal',
value: ''
};
case 'Boolean':
return {
value: 'true'
};
default:
return {
op: 'set'
};
}
}
================================================
FILE: lib/shared/helpers/schema-link-actions.js
================================================
import forEach from 'lodash.foreach';
import Utils from 'helpers/utils';
export default function (pageBuilder, linkedElement, property) {
const actionsValues = [];
const actionsLabels = [];
const ElementClass = pageBuilder.elements[linkedElement.tag];
// Content
if (linkedElement.tag === 'TextBox') {
actionsLabels.push('Content');
actionsValues.push('children');
}
// Settings
if (ElementClass.propsSchema && ElementClass.propsSchema.length > 0) {
// Check settings that match property type
const propsList = Utils.getPropSchemaList(ElementClass.propsSchema);
forEach(propsList, propSchema => {
if (propSchema.id && propSchema.type === property.type) {
actionsLabels.push(propSchema.label);
actionsValues.push(propSchema.id);
}
});
}
// Display
const extraLabel = (property.type === 'Boolean' ? 'true' : 'not empty');
actionsLabels.push(`Visible (when ${extraLabel})`);
actionsValues.push('show');
actionsLabels.push(`Hidden (when ${extraLabel})`);
actionsValues.push('hide');
return {
labels: actionsLabels,
values: actionsValues
};
}
================================================
FILE: lib/shared/helpers/schema-static-properties.js
================================================
export default [
{
id: 'title',
title: 'Title',
type: 'String'
},
{
id: 'slug',
title: 'Slug',
type: 'String'
},
{
id: 'date',
title: 'Created Date',
type: 'Date'
},
{
id: 'publishedDate',
title: 'Published Date',
type: 'Date'
},
{
id: 'updatedDate',
title: 'Updated Date',
type: 'Date'
}
];
================================================
FILE: lib/shared/helpers/stringify-fields.js
================================================
import forEach from 'lodash.foreach';
export default function stringifyFields (data, keys) {
let result = data;
if (data) {
result = Object.assign({}, data);
forEach(keys, (key) => {
if (result[key]) {
result[key] = JSON.stringify(result[key]);
}
});
}
return result;
}
================================================
FILE: lib/shared/helpers/styles-manager.js
================================================
import find from 'lodash.find';
import forEach from 'lodash.foreach';
import {Stylesheet} from 'relax-jss';
import getElementStyleValues from './get-element-style-values';
class StylesManager {
constructor () {
this.stylesMap = {};
this.singleStylesheet = new Stylesheet();
}
changeStyle (id, styleOptions, options, displayOptions, display, single) {
const stylesheet = single && this.singleStylesheet || new Stylesheet();
const rules = styleOptions.rules(getElementStyleValues(
styleOptions.defaults,
options,
displayOptions,
display
));
const classMap = stylesheet.createRules(rules);
this.stylesMap = Object.assign({}, this.stylesMap, {
[id]: {
options,
displayOptions,
display,
stylesheet,
classMap
}
});
return classMap;
}
getClassMap (id, styleOptions, options, displayOptions, display, single) {
let classMap;
const styleMap = this.stylesMap[id];
if (!styleMap ||
styleMap &&
(styleMap.options !== options ||
display !== 'desktop' &&
styleMap.displayOptions !== displayOptions ||
styleMap.displayOptions &&
styleMap.display !== display)
) {
classMap = this.changeStyle(id, styleOptions, options, displayOptions, display, single);
} else {
classMap = styleMap.classMap;
}
return classMap;
}
getStyleOptions (style, elements) {
let result = style;
if (typeof style === 'string') {
forEach(elements, (element) => {
if (element.style && typeof element.style === 'object' && element.style.type === style) {
result = element.style;
}
});
}
return result;
}
processElement (element, elementProps, ElementClass, styles, elements, display, single = false) {
let classMap = {};
if (ElementClass && ElementClass.style) {
const styleId = elementProps && elementProps.style || 'no_style';
const styleOptions = this.getStyleOptions(ElementClass.style, elements);
if (styleId === 'no_style' && !element.style) {
// Default style
classMap = this.getClassMap(
styleOptions.type,
styleOptions,
styleOptions.defaults,
element.displayStyle,
display,
single
);
} else {
// Apply style
if (styleId === 'no_style') {
classMap = this.getClassMap(
element.id,
styleOptions,
element.style,
element.displayStyle,
display,
single
);
} else {
const style = find(styles, {_id: styleId});
if (style) {
classMap = this.getClassMap(
styleId,
styleOptions,
style.options,
style.displayOptions,
display,
single
);
}
}
}
}
return classMap;
}
}
export default new StylesManager();
================================================
FILE: lib/shared/helpers/stylesheet.js
================================================
import {Stylesheet} from 'relax-jss';
export default new Stylesheet();
================================================
FILE: lib/shared/helpers/utils.js
================================================
import cloneDeep from 'lodash.clonedeep';
import forEach from 'lodash.foreach';
import Colr from 'colr';
import {getColorString} from './colors';
const utils = {
isPercentage (str) {
return str && str[str.length - 1] === '%';
},
roundSnap (value, snaps) {
let result = Math.round(value);
forEach(snaps, (snap) => {
if (value > snap - 1 && value < snap + 1) {
result = snap;
}
});
return result;
},
pointsDistance (p1, p2) {
const distX = p1.x - p2.x;
const distY = p1.y - p2.y;
return Math.sqrt(distX * distX + distY * distY);
},
getPointInLineByPerc (pointA, pointB, perc) {
// PA + t / 100 * (PA - PB)
return {
x: pointA.x + perc / 100 * (pointB.x - pointA.x),
y: pointA.y + perc / 100 * (pointB.y - pointA.y)
};
},
validateGATrackingId (str) {
return /(UA|YT|MO)-\d+-\d+/i.test(str);
},
makeBorder (style, property, border) {
if (border.style !== 'none' && border.width !== 0) {
style[property] = `${border.width}px ${border.style} ${getColorString(border.color)}`;
}
},
applyBorders (style, borderObj) {
if (borderObj && borderObj.top && borderObj.left && borderObj.right && borderObj.bottom) {
if (borderObj.equal) {
this.makeBorder(style, 'border', borderObj.top);
} else {
this.makeBorder(style, 'borderTop', borderObj.top);
this.makeBorder(style, 'borderRight', borderObj.right);
this.makeBorder(style, 'borderBottom', borderObj.bottom);
this.makeBorder(style, 'borderLeft', borderObj.left);
}
}
},
applyTextShadows (style, shadows) {
const map = shadows.map(
(shadow) => `${shadow.x} ${shadow.y} ${shadow.blur} ${getColorString(shadow.color)}`
);
style.textShadow = map.toString();
},
applyBoxShadows (style, shadows) {
const map = shadows.map(
(shadow) => {
const type = shadow.type === 'inset' ? 'inset ' : '';
return `${type}${shadow.x} ${shadow.y} ${shadow.blur} ${shadow.spread} ${getColorString(shadow.color)}`;
}
);
const str = map.toString();
style.boxShadow = str;
},
parseYoutubeURL (url) {
const regExp = /^.*((youtu.be\/)|(v\/)|(\/u\/\w\/)|(embed\/)|(watch\?))\??v?=?([^#&\?]*).*/;
const match = url.match(regExp);
let result;
if (match && match[7].length === 11) {
result = match[7];
} else {
result = false;
}
return result;
},
parseVimeoURL (url) {
const regExp = /^.*(vimeo\.com\/)((channels\/[A-z]+\/)|(groups\/[A-z]+\/videos\/))?([0-9]+)/;
const match = regExp.exec(url);
let result;
if (match && match[5].length === 9) {
result = match[5];
} else {
result = false;
}
return result;
},
parseDailymotionURL (url) {
const regExp = /^.+dailymotion.com\/((video|hub)\/([^_]+))?[^#]*(#video=([^_&]+))?/;
const match = url.match(regExp);
let result;
if (match && match.length > 2) {
result = match[5] || match[3];
} else {
result = false;
}
return result;
},
parseColumnsDisplay (value, numChildren, multiRows, idChanged = -1) {
let parsedValue;
if (value && value.constructor === Array) {
parsedValue = value;
} else {
parsedValue = [];
}
// Check missing
if (parsedValue.length < numChildren) {
let difference = numChildren - parsedValue.length;
for (difference; difference > 0; difference--) {
parsedValue.push({
width: multiRows ? 'block' : 'auto',
break: false,
widthPerc: '50%'
});
}
}
// Check if too much
if (parsedValue.length > numChildren) {
const difference = parsedValue.length - numChildren;
parsedValue.splice(-difference, difference);
}
// Go through all to check rules
if (multiRows && numChildren > 1) {
let previous = 'block';
let i;
for (i = 0; i < parsedValue.length; i++) {
if (previous === 'block' && parsedValue[i].width !== 'block') {
if (i === numChildren - 1) {
if (idChanged === i) {
parsedValue[i - 1].width = 'auto';
} else {
parsedValue[i].width = 'block';
}
} else if (parsedValue[i + 1].width === 'block') {
if (idChanged === i) {
parsedValue[i + 1].width = 'auto';
} else {
parsedValue[i].width = 'block';
}
}
}
if (parsedValue[i].width === 'block' ||
previous === 'block' ||
(i >= 2 && parsedValue[i - 2].width === 'block') ||
i === parsedValue.length - 1) {
parsedValue[i].break = false;
}
if (parsedValue[i].break && i < parsedValue.length - 1) {
if (parsedValue[i + 1].width === 'block') {
if (idChanged === i + 1) {
parsedValue[i].break = false;
} else {
parsedValue[i + 1].width = 'auto';
}
}
}
if (!parsedValue[i].widthPerc) {
parsedValue[i].widthPerc = '50%';
}
previous = parsedValue[i].width;
}
}
// Calculate auto widths
let it;
let it1;
for (it = 0; it < parsedValue.length; it++) {
if (parsedValue[it].width !== 'block') {
let countAutoColumns = 0;
let availableAutoSpace = 100;
for (it1 = it; it1 < parsedValue.length; it1++) {
if ((parsedValue[it1].break && it1 !== it) || parsedValue[it1].width === 'block') {
break;
}
if (parsedValue[it1].width === 'custom') {
availableAutoSpace -= parseInt(parsedValue[it1].widthPerc, 10);
} else if (parsedValue[it1].width === 'auto') {
countAutoColumns ++;
}
}
// calc and apply width
if (countAutoColumns > 0) {
const widthCalc = Math.round(availableAutoSpace / countAutoColumns * 100) / 100;
for (it; it < it1; it++) {
if (parsedValue[it].width === 'auto') {
parsedValue[it].widthPerc = `${widthCalc}%`;
}
}
it--;
} else {
it = it1 - 1;
}
}
}
return parsedValue;
},
getOffsetRect (elem) {
const box = elem.getBoundingClientRect();
const body = document.body;
const docElem = document.documentElement;
const scrollTop = window.pageYOffset || docElem.scrollTop || body.scrollTop;
const scrollLeft = window.pageXOffset || docElem.scrollLeft || body.scrollLeft;
const clientTop = docElem.clientTop || body.clientTop || 0;
const clientLeft = docElem.clientLeft || body.clientLeft || 0;
const top = box.top + scrollTop - clientTop;
const left = box.left + scrollLeft - clientLeft;
return {
top: Math.round(top),
left: Math.round(left),
width: Math.round(box.right - box.left),
height: Math.round(box.bottom - box.top)
};
},
limitNumber (number, min, max) {
let result = number;
if (number < min) {
result = min;
} else if (number > max) {
result = max;
}
return result;
},
hasClass (dom, className) {
return dom.className.indexOf(className) !== -1;
},
addClass (dom, className) {
if (!this.hasClass(dom, className)) {
dom.className = `${dom.className} ${className}`;
}
},
removeClass (dom, className) {
let str = dom.className;
str = str.replace(` ${className}`, '');
str = str.replace(`${className} `, '');
dom.className = str;
},
getBestImageVariation (variations, width, height = 0) {
let variationReturn = false;
forEach(variations, (variation) => {
if (variation.dimension.width >= width &&
variation.dimension.height >= height &&
(variation.dimension.width - width < 100 || variation.dimension.heigh - height < 100)) {
variationReturn = variation;
}
});
return variationReturn;
},
getBestImageUrl (imageId, width = 0, height = 0) {
let result = '';
if (imageId && imageId !== '' && typeof imageId !== 'undefined') {
result = `/api/media/resize/${imageId}/${width}/${height}`;
}
return result;
},
parseQueryUrl (url, query) {
let resultUrl = url;
let count = 0;
forEach(query, (value, key) => {
resultUrl += count === 0 ? '?' : '&';
resultUrl += `${key}=`;
resultUrl += typeof value === 'object' ? JSON.stringify(value) : value;
count ++;
});
return resultUrl;
},
parseQueryString (queryString) {
const params = {};
let i;
let l;
// Split into key/value pairs
const queries = queryString.split('&');
// Convert the array of strings into an object
for (i = 0, l = queries.length; i < l; i++) {
const temp = queries[i].split('=');
params[temp[0]] = temp[1];
}
return params;
},
_getPropSchemaListIt (propsSchema, list, defaultLabel = '') {
let resultList = list;
forEach(propsSchema, (propSchema) => {
if (!propSchema.label && defaultLabel !== '') {
propSchema.label = defaultLabel;
}
resultList.push(propSchema);
if (propSchema.unlocks) {
if (propSchema.unlocks.constructor === Array) {
resultList = this._getPropSchemaListIt(propSchema.unlocks, resultList, propSchema.label);
} else {
forEach(propsSchema.unlocks, (propSchemaUnlocks) => {
resultList = this._getPropSchemaListIt(propSchemaUnlocks, resultList);
});
}
}
});
return resultList;
},
getPropSchemaList (propsSchema) {
return this._getPropSchemaListIt(cloneDeep(propsSchema), []);
},
// Filers font family into more readable state ex: source-sans-pro into source sans pro
filterFontFamily (str) {
return str.replace(/-/g, ' ');
},
// Makes a fvd more readable
filterFVD (fvd) {
let str = '';
// font weight
const weightChar = fvd.charAt(1);
str += `${weightChar}00 `;
// font style
// n: normal (default)
// i: italic
// o: oblique
const styleChar = fvd.charAt(0);
if (styleChar === 'n') {
str += 'normal';
} else if (styleChar === 'i') {
str += 'italic';
} else if (styleChar === 'o') {
str += 'oblique';
}
return str;
},
getRGBA (hex, opacity) {
const colr = Colr.fromHex(hex);
const rgb = colr.toRgbObject();
return `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, ${opacity})`;
},
border (arr, size, style, color, opacity) {
arr.border = `${size} ${style} ${utils.getRGBA(color, parseInt(opacity, 10) / 100.0)}`;
arr.WebkitBackgroundClip = 'padding-box';
arr.backgroundClip = 'padding-box';
},
isClient () {
return typeof document !== 'undefined';
},
rounded (arr, corners) {
arr.WebkitBorderRadius = corners;
arr.MozBorderRadius = corners;
arr.OBorderRadius = corners;
arr.borderRadius = corners;
},
backgroundRGBA (arr, color, opacity) {
arr.backgroundColor = utils.getRGBA(color, parseInt(opacity, 10) / 100.0);
},
transition (arr, to, time, ease) {
const transition = `${to} ${time} ${ease}`;
arr.transition = transition;
arr.WebkitTransition = transition;
arr.MozTransition = transition;
arr.OTransition = transition;
},
translate (arr, x, y) {
const translate = `translate(${x}, ${y})`;
arr.transform = translate;
arr.msTransform = translate;
arr.MozTransform = translate;
arr.WebkitTransform = translate;
arr.OTransform = translate;
},
padding (arr, top, right, bottom, left) {
arr.padding = `${top} ${right} ${bottom} ${left}`;
},
// Return json from a font format fvd (ex. input: i4, n4, n8 ...) https://github.com/typekit/fvd
processFVD (style, fvd) {
style.fontStyle = 'normal';
style.fontWeight = 400;
// font style
// n: normal (default)
// i: italic
// o: oblique
const styleChar = fvd.charAt(0);
if (styleChar === 'i') {
style.fontStyle = 'italic';
} else if (styleChar === 'o') {
style.fontStyle = 'oblique';
}
// font weight
const weightChar = fvd.charAt(1);
style.fontWeight = parseInt(`${weightChar}00`, 10);
},
getElementsSchemaLinks (schemaLinks) {
const elementsLinks = {};
if (schemaLinks) {
forEach(schemaLinks, (links, propertyId) => {
forEach(links, (link) => {
elementsLinks[link.elementId] = elementsLinks[link.elementId] || [];
elementsLinks[link.elementId].push({
propertyId,
action: link.action
});
});
});
}
return elementsLinks;
},
alterSchemaElementProps (links, element, schemaEntry, elementProps = {}) {
const changes = {};
forEach(links, (link) => {
const splitted = link.propertyId.split('#');
let schemaEntryValue = schemaEntry;
forEach(splitted, (pathKey) => {
schemaEntryValue = schemaEntryValue[pathKey];
});
if (link.action === 'children') {
if (schemaEntryValue) {
changes.children = schemaEntryValue;
} else {
changes.display = false;
}
} else if (link.action === 'show' && (!schemaEntryValue || schemaEntryValue === '')) {
changes.display = false;
} else if (link.action === 'hide' && schemaEntryValue && schemaEntryValue !== '') {
changes.display = false;
} else if (link.action) { // setting
elementProps[link.action] = schemaEntryValue;
}
});
return Object.assign({}, element, changes);
}
};
export default utils;
================================================
FILE: lib/shared/reducers/admin-menu.js
================================================
import actionTypes from 'actions';
const defaultState = true;
export default function adminMenuReducer (state = defaultState, action = {}) {
switch (action.type) {
case actionTypes.openAdminMenu:
return true;
case actionTypes.closeAdminMenu:
return false;
default:
return state;
}
}
================================================
FILE: lib/shared/reducers/display.js
================================================
import actionTypes from 'actions';
const defaultState = 'desktop';
export default function displayReducer (state = defaultState, action = {}) {
switch (action.type) {
case actionTypes.changeDisplay:
return action.value;
default:
return state;
}
}
================================================
FILE: lib/shared/reducers/dnd.js
================================================
import actionTypes from 'actions';
const defaultState = {
dragging: false,
draggingData: {},
dragInfo: false,
dropInfo: false,
droppableOrientation: 'vertical'
};
export default function dndReducer (state = defaultState, action = {}) {
switch (action.type) {
case actionTypes.startDragging:
return Object.assign({}, state, {
dragging: true,
draggingData: action.draggingData,
dragInfo: action.dragInfo
});
case actionTypes.onDroppable:
return Object.assign({}, state, {
dropInfo: action.dropInfo
});
case actionTypes.outDroppable:
if (state.dropInfo && state.dropInfo.id === action.id) {
return Object.assign({}, state, {
dropInfo: false
});
}
return state;
case actionTypes.stopDragging:
return Object.assign({}, defaultState);
default:
return state;
}
}
================================================
FILE: lib/shared/reducers/fonts.js
================================================
import actionTypes from 'actions';
import cloneDeep from 'lodash.clonedeep';
import forEach from 'lodash.foreach';
import Relate from 'relate-js';
const defaultState = {
data: {
previewText: 'Abc',
previewLayout: 'grid', // grid || list
input: {
google: {
input: '',
valid: false
},
typekit: {
input: '',
valid: false
},
fontdeck: {
input: '',
valid: false
},
monotype: {
input: '',
valid: false
},
custom: {
input: '',
valid: false
}
},
customFonts: [],
fonts: {},
webfontloader: {}
},
newCustom: null,
errors: null
};
function parseSettings (_settings) {
const settings = {};
forEach(_settings, (setting) => {
settings[setting._id] = setting.value;
});
return settings;
}
export default function fontsReducer (state = defaultState, action = {}) {
switch (action.type) {
case Relate.actionTypes.query:
if (action.data.settings) {
const parsed = parseSettings(action.data.settings);
if (parsed.fonts) {
return Object.assign({}, state, {
data: JSON.parse(parsed.fonts),
errors: action.errors
});
}
}
return state;
case actionTypes.changeFontsPreviewText:
return Object.assign({}, state, {
data: Object.assign({}, state.data, {
previewText: action.value
})
});
case actionTypes.changeFontsPreviewLayout:
return Object.assign({}, state, {
data: Object.assign({}, state.data, {
previewLayout: action.value
})
});
case actionTypes.loadFonts:
return Object.assign({}, state, {
data: Object.assign({}, state.data, {
fonts: action.fonts
})
});
case actionTypes.submitCustomFont:
return Object.assign({}, state, {
data: Object.assign({}, state.data, {
customFonts: [...state.data.customFonts, action.data.submitCustomFont]
}),
newCustom: action.data.submitCustomFont,
errors: action.errors
});
case actionTypes.customFontIncluded: {
const fontsDataClone = cloneDeep(state.data);
fontsDataClone.webfontloader.custom = fontsDataClone.webfontloader.custom || {families: []};
fontsDataClone.webfontloader.custom.families.push(state.newCustom.family);
return Object.assign({}, state, {
data: fontsDataClone,
newCustom: null
});
}
case actionTypes.removeCustomFont: {
const fontsDataRemoveClone = cloneDeep(state.data);
const id = action.data.removeCustomFont.id;
forEach(fontsDataRemoveClone.customFonts, (obj, index) => {
if (obj.id === id) {
fontsDataRemoveClone.customFonts.splice(index, 1);
const ind = fontsDataRemoveClone.webfontloader.custom.families.indexOf(obj.family);
if (ind !== -1) {
fontsDataRemoveClone.webfontloader.custom.families.splice(ind, 1);
if (fontsDataRemoveClone.webfontloader.custom.families.length === 0) {
delete fontsDataRemoveClone.webfontloader.custom;
}
}
return false;
}
});
return Object.assign({}, state, {
data: fontsDataRemoveClone
});
}
case actionTypes.changeFontInput: {
const fontsClone = cloneDeep(state.data);
const tab = action.tab;
const value = action.value;
let nowValid = false;
let previousValid;
if (tab === 0) {
// Google fonts validation
previousValid = state.data.input.google.valid;
fontsClone.input.google.input = value;
fontsClone.input.google.valid = false;
const paramsStr = value.split('?');
// Valid if has params
if (paramsStr.length === 2) {
const params = {};
const re = /[?&]?([^=]+)=([^&]*)/g;
let tokens = re.exec(paramsStr[1]);
while (tokens) {
params[decodeURIComponent(tokens[1])] = decodeURIComponent(tokens[2]);
tokens = re.exec(paramsStr[1]);
}
if (params.family) {
fontsClone.input.google.valid = true;
nowValid = true;
if (!fontsClone.webfontloader.google) {
fontsClone.webfontloader.google = {
families: []
};
} else {
fontsClone.webfontloader.google.families = [];
}
// Object {family: "Lobster|Open+Sans:400,700", subset: "latin,cyrillic-ext,cyrillic"}
const families = params.family.split('|');
for (let i = 0; i < families.length; i++) {
let googleFont = families[i];
// Might not have multiple weights
if (families[i].indexOf(':') === -1) {
googleFont += ':';
}
if (params.subset) {
googleFont += `:${params.subset}`;
} else {
googleFont += ':latin';
}
fontsClone.webfontloader.google.families.push(googleFont);
}
}
}
if (!nowValid && fontsClone.webfontloader.google) {
delete fontsClone.webfontloader.google;
}
} else if (tab === 1) {
// Typekit validation
previousValid = state.data.input.typekit.valid;
fontsClone.input.typekit.input = value;
fontsClone.input.typekit.valid = false;
if (value.length === 7) {
nowValid = true;
fontsClone.input.typekit.valid = true;
fontsClone.webfontloader.typekit = fontsClone.webfontloader.typekit || {};
fontsClone.webfontloader.typekit.id = value;
}
if (!nowValid && fontsClone.webfontloader.typekit) {
delete fontsClone.webfontloader.typekit;
}
} else if (tab === 2) {
// Fonts.com (monotype) validation
previousValid = state.data.input.monotype.valid;
fontsClone.input.monotype.input = value;
fontsClone.input.monotype.valid = false;
if (value.length === 36) {
const regex = new RegExp(/[0-9|a-z]{8}-[0-9|a-z]{4}-[0-9|a-z]{4}-[0-9|a-z]{4}-[0-9|a-z]{12}/g);
fontsClone.input.monotype.valid = regex.test(value);
}
// valid
if (fontsClone.input.monotype.valid) {
nowValid = true;
fontsClone.webfontloader.monotype = fontsClone.webfontloader.monotype || {};
fontsClone.webfontloader.monotype.projectId = value;
} else if (fontsClone.webfontloader.monotype) {
delete fontsClone.webfontloader.monotype;
}
} else if (tab === 3) {
// Font Deck validation
previousValid = state.data.input.fontdeck.valid;
fontsClone.input.fontdeck.input = value;
fontsClone.input.fontdeck.valid = false;
if (value.length === 5) {
nowValid = true;
fontsClone.input.fontdeck.valid = true;
fontsClone.webfontloader.fontdeck = fontsClone.webfontloader.fontdeck || {};
fontsClone.webfontloader.fontdeck.id = value;
}
if (!nowValid && fontsClone.webfontloader.fontdeck) {
delete fontsClone.webfontloader.fontdeck;
}
}
if (previousValid && !nowValid || nowValid) {
// TODO re load fonts
}
return Object.assign({}, state, {
data: fontsClone
});
}
default:
return state;
}
}
================================================
FILE: lib/shared/reducers/index.js
================================================
import {combineReducers} from 'redux';
import {routerStateReducer as router} from 'redux-router';
import {relateReducer} from 'relate-js';
import adminMenu from './admin-menu';
import display from './display';
import dnd from './dnd';
import fonts from './fonts';
import media from './media';
import menu from './menu';
import pageBuilder from './page-builder';
import revisions from './revisions';
import schema from './schema';
import schemaEntry from './schema-entry';
import session from './session';
import settings from './settings';
import styles from './styles';
import symbols from './symbols';
const rootReducer = combineReducers({
relateReducer,
adminMenu,
display,
dnd,
fonts,
media,
menu,
pageBuilder,
revisions,
schema,
schemaEntry,
settings,
styles,
symbols,
session,
router
});
export default rootReducer;
================================================
FILE: lib/shared/reducers/media.js
================================================
import actionTypes from 'actions';
import findIndex from 'lodash.findindex';
const defaultState = {
display: 'grid',
uploads: []
};
function changeUploadStatus ({state, id, status}) {
const uploads = state.uploads.slice(0);
const index = findIndex(uploads, ['id', id]);
if (index !== -1) {
uploads[index] = Object.assign({}, uploads[index], {
status
});
}
return Object.assign({}, state, {uploads});
}
export default function mediaReducer (state = defaultState, action = {}) {
switch (action.type) {
case actionTypes.addFilesToUpload: {
const files = action.files.map((file) => ({
id: file.id,
name: file.name,
status: 'queue' // queue || uploading || success || error
}));
return Object.assign({}, state, {
uploads: state.uploads.concat(files)
});
}
case actionTypes.changeMediaDisplay:
return Object.assign({}, state, {
display: action.display
});
case actionTypes.uploadingMedia:
return changeUploadStatus({
state,
id: action.fileId,
status: 'uploading'
});
case actionTypes.mediaUploadSuccess:
return changeUploadStatus({
state,
id: action.fileId,
status: 'success'
});
case actionTypes.mediaUploadError:
return changeUploadStatus({
state,
id: action.fileId,
status: 'error'
});
default:
return state;
}
}
================================================
FILE: lib/shared/reducers/menu.js
================================================
import actionTypes from 'actions';
import Relate from 'relate-js';
const defaultState = {};
let ID = 0;
export default function getLastId (data) {
while (data[ID]) {
ID++;
}
return (ID++).toString();
}
export default function menuReducer (state = defaultState, action = {}) {
switch (action.type) {
case Relate.actionTypes.query:
if (action.data.menu && action.data.menu.data) {
return JSON.parse(action.data.menu.data);
}
return state;
case actionTypes.addMenuItem: { // new menu item
const destId = action.destination.id;
const destPos = action.destination.position;
const id = getLastId(state);
const changes = {
[id]: Object.assign({id, parent: destId}, action.item)
};
if (!state[destId] && destId === 'root') {
// create root
changes.root = {
id: 'root',
children: [id]
};
} else {
const children = state[destId] && state[destId].children || [];
children.splice(destPos, 0, id);
changes[destId] = Object.assign({}, state[destId], {
children
});
}
return Object.assign({}, state, changes);
}
case actionTypes.moveMenuItem: {
const destId = action.destination.id;
let destPos = action.destination.position;
const sourceId = action.id;
const sourceElement = state[sourceId];
// remove element from current position
const parentElement = state[sourceElement.parent];
const parentChildren = parentElement.children.slice(0);
const oldPosition = parentElement.children.indexOf(sourceId);
parentChildren.splice(oldPosition, 1);
const changes = {
[parentElement.id]: Object.assign({}, parentElement, {
children: parentChildren
})
};
// change source parent value
if (sourceElement.parent !== destId) {
changes[sourceId] = Object.assign({}, sourceElement, {
parent: destId
});
} else if (oldPosition < destPos) {
destPos--;
}
// change destination
const children =
changes[destId] && changes[destId].children ||
state[destId].children && state[destId].children.slice(0) ||
[];
children.splice(destPos, 0, sourceId);
changes[destId] = Object.assign({}, changes[destId] || state[destId], {
children
});
return Object.assign({}, state, changes);
}
default:
return state;
}
}
================================================
FILE: lib/shared/reducers/page-builder-actions/add.js
================================================
import forEach from 'lodash.foreach';
import getId from './helpers/get-id';
export default (data, action) => {
const destinationId = action.destination.id;
const destinationPosition = action.destination.position;
const sourceId = action.element.id || getId(data);
const changes = {
[sourceId]: Object.assign({
id: sourceId,
parent: destinationId
}, action.element)
};
if (action.childrenElements) {
forEach(action.childrenElements, (childElement) => {
changes[childElement.id] = Object.assign({}, childElement);
});
}
const children = changes[destinationId] && changes[destinationId].children ||
data[destinationId].children && data[destinationId].children.slice(0) ||
[];
children.splice(destinationPosition, 0, sourceId);
changes[destinationId] = Object.assign({}, data[destinationId], {
children
});
return {
data: Object.assign({}, data, changes),
revertAction: {
type: 'remove',
elementId: sourceId
}
};
};
================================================
FILE: lib/shared/reducers/page-builder-actions/change-animation.js
================================================
export default (data, action) => {
const defaults = {
use: false,
effect: 'transition.fadeIn',
duration: 400,
delay: 300
};
const element = data[action.elementId];
const previousValue = element.animation && element.animation[action.property];
const changedElement = Object.assign({}, element, {
animation: Object.assign({}, element.animation || defaults, {
[action.property]: action.value
})
});
return {
data: Object.assign({}, data, {
[action.elementId]: changedElement
}),
revertAction: Object.assign({}, action, {
value: previousValue
})
};
};
================================================
FILE: lib/shared/reducers/page-builder-actions/change-children.js
================================================
import forEach from 'lodash.foreach';
import getId from './helpers/get-id';
export default (data, action) => {
const element = data[action.elementId];
const previousChildren = [];
const newData = Object.assign({}, data);
if (element.children && element.children.constructor === Array) {
forEach(element.children, (child) => {
previousChildren.push(Object.assign({}, data[child.id]));
delete newData[child.id];
});
}
const changes = {};
const newChildren = [];
forEach(action.children, (child) => {
const childId = getId(data);
newChildren.push(childId);
const newChild = Object.assign({}, child, {
id: childId,
parent: element.id
});
changes[childId] = newChild;
});
changes[element.id] = Object.assign({}, element, {
children: newChildren
});
return {
data: Object.assign(newData, changes),
revertAction: Object.assign({}, action, {
children: previousChildren
})
};
};
================================================
FILE: lib/shared/reducers/page-builder-actions/change-content.js
================================================
export default (data, action) => {
const element = data[action.elementId];
const previousValue = element.children || '';
const changedElement = Object.assign({}, element, {
children: action.value
});
return {
data: Object.assign({}, data, {
[action.elementId]: changedElement
}),
revertAction: Object.assign({}, action, {
value: previousValue
})
};
};
================================================
FILE: lib/shared/reducers/page-builder-actions/change-display.js
================================================
// Changes an element 'visible on' property
// action:
// - elementId
// - display
export default (data, action) => {
const element = data[action.elementId];
const previousValue = element.hide && element.hide[action.display] && true;
const changedElement = Object.assign({}, element, {
hide: Object.assign({}, element.hide || {}, {
[action.display]: !previousValue
})
});
return {
data: Object.assign({}, data, {
[action.elementId]: changedElement
}),
revertAction: Object.assign({}, action)
};
};
================================================
FILE: lib/shared/reducers/page-builder-actions/change-label.js
================================================
export default (data, action) => {
const element = data[action.elementId];
const previousValue = element.label || element.tag;
return {
data: Object.assign({}, data, {
[action.elementId]: Object.assign({}, element, {
label: action.value
})
}),
revertAction: Object.assign({}, action, {
value: previousValue
})
};
};
================================================
FILE: lib/shared/reducers/page-builder-actions/change-position.js
================================================
export default (data, action) => {
const defaults = {
position: 'static',
top: 'auto',
right: 'auto',
bottom: 'auto',
left: 'auto',
zIndex: '1'
};
const element = data[action.elementId];
let previousValue;
let changedElement;
if (action.display === 'desktop') {
previousValue = element.position && element.position[action.property];
changedElement = Object.assign({}, element, {
position: Object.assign({}, element.position || defaults, {
[action.property]: action.value
})
});
} else {
previousValue =
element.displayPosition &&
element.displayPosition[action.display] &&
element.displayPosition[action.display][action.property];
changedElement = Object.assign({}, element, {
displayPosition: Object.assign({}, element.displayPosition || {}, {
[action.display]: Object.assign(
{},
element.displayPosition && element.displayPosition[action.display] || {},
{
[action.property]: action.value
}
)
})
});
}
return {
data: Object.assign({}, data, {
[action.elementId]: changedElement
}),
revertAction: Object.assign({}, action, {
value: previousValue
})
};
};
================================================
FILE: lib/shared/reducers/page-builder-actions/change-prop.js
================================================
// Changes an element prop
// action:
// - type
// - elementId
// - property
// - value
// - display
export default (data, action) => {
const element = data[action.elementId];
let previousValue;
let changedElement;
if (action.display === 'desktop') {
if (action.property === 'children') {
previousValue = element.children || '';
changedElement = Object.assign({}, element, {
children: action.value,
props: Object.assign({}, element.props || {}, {
[action.property]: action.value
})
});
} else {
previousValue = element.props && element.props[action.property];
changedElement = Object.assign({}, element, {
props: Object.assign({}, element.props || {}, {
[action.property]: action.value
})
});
}
} else {
previousValue =
element.displayProps &&
element.displayProps[action.display] &&
element.displayProps[action.display][action.property];
changedElement = Object.assign({}, element, {
displayProps: Object.assign({}, element.displayProps || {}, {
[action.display]: Object.assign({}, element.displayProps && element.displayProps[action.display] || {}, {
[action.property]: action.value
})
})
});
}
return {
data: Object.assign({}, data, {
[action.elementId]: changedElement
}),
revertAction: Object.assign({}, action, {
value: previousValue
})
};
};
================================================
FILE: lib/shared/reducers/page-builder-actions/change-style.js
================================================
// Changes an element custom style property (no-style only)
// action:
// - type
// - elementId
// - property
// - value
// - display
export default (data, action) => {
const element = data[action.elementId];
let previousValue;
let changedElement;
if (action.display === 'desktop') {
previousValue = element.style && element.style[action.property];
changedElement = Object.assign({}, element, {
style: Object.assign({}, element.style || {}, {
[action.property]: action.value
})
});
if (action.value === null || typeof action.value === 'undefined') {
delete changedElement.style[action.property];
}
} else {
previousValue =
element.displayStyle &&
element.displayStyle[action.display] &&
element.displayStyle[action.display][action.property];
changedElement = Object.assign({}, element, {
displayStyle: Object.assign({}, element.displayStyle || {}, {
[action.display]: Object.assign({}, element.displayStyle && element.displayStyle[action.display] || {}, {
[action.property]: action.value
})
})
});
if (action.value === null || typeof action.value === 'undefined') {
delete changedElement.displayStyle[action.display][action.property];
}
}
return {
data: Object.assign({}, data, {
[action.elementId]: changedElement
}),
revertAction: Object.assign({}, action, {
value: previousValue
})
};
};
================================================
FILE: lib/shared/reducers/page-builder-actions/duplicate.js
================================================
import cloneDeep from 'lodash.clonedeep';
import cloneChildren from './helpers/clone-children';
import getId from './helpers/get-id';
export default (data, action) => {
const sourceId = getId(data);
const changes = {};
const toDuplicate = data[action.elementId];
const destinationId = toDuplicate.parent;
const destinationPosition = data[toDuplicate.parent].children.indexOf(toDuplicate.id) + 1;
const cloned = cloneDeep(toDuplicate);
cloned.id = sourceId;
if (cloned.children && cloned.children.constructor === Array) {
cloned.children = cloneChildren(cloned.children, data, changes, sourceId);
}
changes[sourceId] = cloned;
const children =
changes[destinationId] && changes[destinationId].children ||
data[destinationId].children && data[destinationId].children.slice(0) ||
[];
children.splice(destinationPosition, 0, sourceId);
changes[destinationId] = Object.assign({}, data[destinationId], {
children
});
return {
data: Object.assign({}, data, changes),
revertAction: {
type: 'remove',
elementId: sourceId
}
};
};
================================================
FILE: lib/shared/reducers/page-builder-actions/element-add-schema-link.js
================================================
export default (data, action) => {
const element = data[action.elementId];
const newLink = {
elementId: action.linkElementId,
action: action.action
};
const currentSchemaLinks = element.props && element.props.schemaLinks || {};
const schemaLinksChanged = Object.assign({}, currentSchemaLinks, {
[action.propertyId]: [...(currentSchemaLinks[action.propertyId] || []), newLink]
});
const changedElement = Object.assign({}, element, {
props: Object.assign({}, element.props || {}, {
schemaLinks: schemaLinksChanged
})
});
return {
data: Object.assign({}, data, {
[action.elementId]: changedElement
}),
revertAction: {
type: 'elementRemoveSchemaLink',
elementId: action.elementId,
propertyId: action.propertyId,
index: schemaLinksChanged[action.propertyId].length - 1
}
};
};
================================================
FILE: lib/shared/reducers/page-builder-actions/element-change-schema-link-action.js
================================================
export default (data, action) => {
const element = data[action.elementId];
const currentSchemaLinks = element.props.schemaLinks;
const newProperties = currentSchemaLinks[action.propertyId].slice(0);
const oldAction = newProperties[action.index].action;
newProperties.splice(action.index, 1, Object.assign({}, newProperties[action.index], {
action: action.value
}));
const schemaLinksChanged = Object.assign({}, currentSchemaLinks, {
[action.propertyId]: newProperties
});
const changedElement = Object.assign({}, element, {
props: Object.assign({}, element.props || {}, {
schemaLinks: schemaLinksChanged
})
});
return {
data: Object.assign({}, data, {
[action.elementId]: changedElement
}),
revertAction: Object.assign({}, action, {
value: oldAction
})
};
};
================================================
FILE: lib/shared/reducers/page-builder-actions/element-remove-schema-link.js
================================================
export default (data, action) => {
const element = data[action.elementId];
const currentSchemaLinks = element.props.schemaLinks;
const newProperties = currentSchemaLinks[action.propertyId].slice(0);
const removedProperty = (newProperties.splice(action.index, 1))[0];
const schemaLinksChanged = Object.assign({}, currentSchemaLinks, {
[action.propertyId]: newProperties
});
const changedElement = Object.assign({}, element, {
props: Object.assign({}, element.props || {}, {
schemaLinks: schemaLinksChanged
})
});
return {
data: Object.assign({}, data, {
[action.elementId]: changedElement
}),
revertAction: {
type: 'elementAddSchemaLink',
elementId: action.elementId,
propertyId: action.propertyId,
linkElementId: removedProperty.elementId,
action: removedProperty.action
}
};
};
================================================
FILE: lib/shared/reducers/page-builder-actions/helpers/clone-children.js
================================================
import cloneDeep from 'lodash.clonedeep';
import forEach from 'lodash.foreach';
import getId from './get-id';
export default function cloneChildren (children, data, changes, parentId) {
const resultChildren = [];
forEach(children, (childId) => {
const newId = getId(data);
const element = data[childId];
const clonedChild = cloneDeep(element);
clonedChild.id = newId;
clonedChild.parent = parentId;
if (clonedChild.children && clonedChild.children.constructor === Array) {
clonedChild.children = cloneChildren(clonedChild.children, data, changes, newId);
}
changes[newId] = clonedChild;
resultChildren.push(newId);
});
return resultChildren;
}
================================================
FILE: lib/shared/reducers/page-builder-actions/helpers/get-id.js
================================================
let ID_COUNTER = 0;
export default function getLastId (data) {
while (data[ID_COUNTER]) {
ID_COUNTER++;
}
return (ID_COUNTER++).toString();
}
================================================
FILE: lib/shared/reducers/page-builder-actions/helpers/remove-children.js
================================================
import forEach from 'lodash.foreach';
export default function removeChildren (children, copiedData) {
const childrenElements = {};
forEach(children, (childId) => {
const element = copiedData[childId];
childrenElements[element.id] = Object.assign({}, element);
if (element.children && element.children.constructor === Array) {
Object.assign(childrenElements, removeChildren(element.children, copiedData));
}
delete copiedData[childId];
});
return childrenElements;
}
================================================
FILE: lib/shared/reducers/page-builder-actions/index.js
================================================
import add from './add';
import changeAnimation from './change-animation';
import changeChildren from './change-children';
import changeContent from './change-content';
import changeDisplay from './change-display';
import changeLabel from './change-label';
import changePosition from './change-position';
import changeProp from './change-prop';
import changeStyle from './change-style';
import duplicate from './duplicate';
import elementAddSchemaLink from './element-add-schema-link';
import elementChangeSchemaLinkAction from './element-change-schema-link-action';
import elementRemoveSchemaLink from './element-remove-schema-link';
import makeDynamic from './make-dynamic';
import move from './move';
import newAction from './new';
import remove from './remove';
export default {
new: newAction,
move,
add,
duplicate,
remove,
makeDynamic,
changeLabel,
changeDisplay,
changeProp,
changeAnimation,
changePosition,
changeStyle,
changeContent,
changeChildren,
elementAddSchemaLink,
elementRemoveSchemaLink,
elementChangeSchemaLinkAction
};
================================================
FILE: lib/shared/reducers/page-builder-actions/make-dynamic.js
================================================
import getId from './helpers/get-id';
export default (data, action) => {
const element = data[action.elementId];
const destinationId = element.parent;
const destinationPosition = data[destinationId].children.indexOf(element.id);
// Add new dynamic element
const dynamicElementId = getId(data);
const newChildren = data[destinationId].children.slice(0);
newChildren.splice(destinationPosition, 1, dynamicElementId);
const changes = {
[dynamicElementId]: {
parent: destinationId,
id: dynamicElementId,
tag: 'DynamicList',
children: [element.id]
},
[destinationId]: Object.assign({}, data[destinationId], {
children: newChildren
}),
[element.id]: Object.assign({}, element, {
parent: dynamicElementId
})
};
return {
data: Object.assign({}, data, changes),
revertAction: [
{
type: 'move',
source: {
id: element.id
},
destination: {
id: destinationId,
position: destinationPosition
}
},
{
type: 'remove',
elementId: dynamicElementId
}
]
};
};
================================================
FILE: lib/shared/reducers/page-builder-actions/move.js
================================================
export default (data, action) => {
let destinationPosition = action.destination.position;
const destinationId = action.destination.id;
const sourceId = action.source.id;
const sourceElement = data[sourceId];
// remove from old parent children
const parentElement = data[sourceElement.parent];
const parentChildren = parentElement.children.slice(0);
const oldPosition = parentElement.children.indexOf(sourceId);
parentChildren.splice(oldPosition, 1);
const changes = {
[parentElement.id]: Object.assign({}, parentElement, {
children: parentChildren
})
};
if (sourceElement.parent !== destinationId) {
changes[sourceId] = Object.assign({}, sourceElement, {
parent: destinationId
});
} else if (oldPosition < destinationPosition) {
destinationPosition--;
}
const children =
changes[destinationId] && changes[destinationId].children ||
data[destinationId].children && data[destinationId].children.slice(0) ||
[];
children.splice(destinationPosition, 0, sourceId);
changes[destinationId] = Object.assign({}, data[destinationId], {
children
});
return {
data: Object.assign({}, data, changes),
revertAction: {
type: 'move',
source: {
id: action.source.id
},
destination: {
id: sourceElement.parent,
position: oldPosition
}
}
};
};
================================================
FILE: lib/shared/reducers/page-builder-actions/new.js
================================================
import elements from 'elements';
import forEach from 'lodash.foreach';
import getId from './helpers/get-id';
export default (data, action) => {
const destinationId = action.destination.id;
const destinationPosition = action.destination.position;
const sourceId = getId(data);
const changes = {
[sourceId]: {
parent: destinationId,
id: sourceId,
...action.element
}
};
const ElementClass = elements[action.element.tag];
if (ElementClass && ElementClass.defaultChildren) {
const defaultChildren = ElementClass.defaultChildren;
if (defaultChildren.constructor === Array) {
forEach(defaultChildren, (child) => {
const childId = getId(data);
changes[childId] = Object.assign({}, child, {
parent: sourceId,
id: childId
});
changes[sourceId].children = changes[sourceId].children || [];
changes[sourceId].children.push(childId);
});
}
}
// Destination
if (!data[destinationId] && destinationId === 'body') {
changes.body = {
id: 'body',
tag: 'body',
children: [sourceId]
};
} else {
const children =
changes[destinationId] && changes[destinationId].children ||
data[destinationId].children && data[destinationId].children.slice(0) ||
[];
children.splice(destinationPosition, 0, sourceId);
changes[destinationId] = Object.assign({}, data[destinationId], {
children
});
}
return {
data: Object.assign({}, data, changes),
revertAction: {
type: 'remove',
elementId: sourceId
}
};
};
================================================
FILE: lib/shared/reducers/page-builder-actions/remove.js
================================================
import removeChildren from './helpers/remove-children';
export default (data, action) => {
const element = data[action.elementId];
const parentChildren = data[element.parent].children.slice(0);
const positionInParent = parentChildren.indexOf(element.id);
parentChildren.splice(positionInParent, 1);
const copiedData = Object.assign({}, data, {
[element.parent]: Object.assign({}, data[element.parent], {
children: parentChildren
})
});
let childrenElements = null; // For revert action
if (element.children && element.children.constructor === Array) {
childrenElements = removeChildren(element.children, copiedData);
}
const removedElement = copiedData[action.elementId];
delete copiedData[action.elementId];
return {
data: copiedData,
revertAction: {
type: 'add',
element: removedElement,
childrenElements,
destination: {
id: element.parent,
position: positionInParent
}
}
};
};
================================================
FILE: lib/shared/reducers/page-builder.js
================================================
import actionTypes from 'actions';
import elements from 'elements';
import forEach from 'lodash.foreach';
import Relate from 'relate-js';
import pageBuilderActions from './page-builder-actions';
const categories = [
'structure',
'content',
'media',
'form'
];
forEach(elements, (element) => {
if (element.settings && element.settings.category) {
if (categories.indexOf(element.settings.category) === -1) {
categories.push(element.settings.category);
}
}
});
categories.push('other');
const categoriesCollapsed = {};
forEach(categories, (category) => {categoriesCollapsed[category] = false;});
const defaultState = {
data: {},
actions: [],
redos: [],
expanded: {}, // for structure layer
userExpanded: {}, // for structure layer
editing: true,
elements,
selectedId: null,
selectedElement: null,
selectedParent: null,
selectedPath: [],
overedId: null,
focusElementId: null,
linkingData: false,
linkingDataElementId: null,
linkingDataElement: null,
linkingFormData: false,
linkingFormDataElementId: null,
categories,
categoriesCollapsed,
elementsMenuOpened: false,
elementsMenuOptions: {},
elementsMenuSpot: null,
menuTab: 'style'
};
function doAction (data, action) {
let result;
if (action && action.constructor === Array) {
const revertActions = [];
let resultData = data;
forEach(action, (actionPart) => {
const actionFunction = pageBuilderActions[actionPart.type];
const partResult = actionFunction && actionFunction(resultData, actionPart);
if (partResult) {
resultData = partResult.data;
revertActions.unshift(partResult.revertAction);
}
});
result = {
data: resultData,
revertAction: revertActions
};
} else {
const actionFunction = pageBuilderActions[action.type];
result = actionFunction && actionFunction(data, action);
}
return result;
}
function getElementPath (selectedElement, data) {
const result = [];
let current = selectedElement;
while (current.parent) {
current = data[current.parent];
result.unshift(current);
}
return result;
}
function inPath (elementId, targetId, data) {
let found = false;
let current = data[elementId];
while (!found && current.parent) {
if (current.parent === targetId) {
found = true;
}
current = data[current.parent];
}
return found;
}
function checkOverAndSelected (state) {
const changes = {
selectedId: state.selectedId,
overedId: state.overedId
};
// Check if inside the focused element
if (state.focusElementId) {
if (state.overedId && !inPath(state.overedId, state.focusElementId, state.data)) {
changes.overedId = null;
}
if (state.selectedId && !inPath(state.selectedId, state.focusElementId, state.data)) {
changes.selectedId = null;
}
}
if (state.selectedId && changes.selectedId) {
const selectedElement = state.data[state.selectedId];
changes.selectedElement = selectedElement;
changes.selectedParent = selectedElement && selectedElement.parent;
changes.selectedPath = selectedElement && getElementPath(selectedElement, state.data);
changes.selectedId = selectedElement && state.selectedId;
}
if (state.linkingDataElementId) {
const linkingDataElement = state.data[state.linkingDataElementId];
changes.linkingDataElement = linkingDataElement;
changes.linkingDataElementId = linkingDataElement && state.linkingDataElementId;
}
return Object.assign({}, state, changes);
}
export default function pageBuilderReducer (state = defaultState, action = {}) {
switch (action.type) {
case Relate.actionTypes.query:
if (action.data.draft) {
const dataChange = {};
if (action.data.draft.data) {
dataChange.data = JSON.parse(action.data.draft.data);
}
if (action.data.draft.actions) {
dataChange.actions = JSON.parse(action.data.draft.actions);
}
return Object.assign({}, defaultState, dataChange);
}
return state;
case actionTypes.pbDoAction: {
const actionResult = doAction(state.data, action.action);
return checkOverAndSelected(Object.assign({}, state, {
data: actionResult.data,
actions: [...state.actions, actionResult.revertAction],
redos: []
}));
}
case actionTypes.pbUndoAction: {
if (state.data.actions.length > 0) {
const actions = state.actions.slice(0);
const undoActionResult = doAction(state.data, actions.pop());
return checkOverAndSelected(Object.assign({}, state, {
redos: [...state.redos, undoActionResult.revertAction],
data: undoActionResult.data,
actions
}));
}
return state;
}
case actionTypes.pbRedoAction:
if (state.redos.length > 0) {
const redos = state.redos.slice(0);
const redoActionResult = doAction(state.data, redos.pop());
return checkOverAndSelected(Object.assign({}, state, {
redos,
data: redoActionResult.data,
actions: [...state.actions, redoActionResult.revertAction]
}));
}
return state;
case actionTypes.makeElementSymbol: {
const elementToSymbol = state.data[action.params.elementId];
const elementToSymbolParent = state.data[elementToSymbol.parent];
const elementToSymbolPosition = elementToSymbolParent.children.indexOf(action.params.elementId);
const makeElementSymbolActionResult = doAction(state.data, [
{
type: 'remove',
elementId: action.params.elementId
},
{
type: 'new',
destination: {
id: elementToSymbol.parent,
position: elementToSymbolPosition
},
element: {
tag: 'Symbol',
props: {
symbolId: action.data.addSymbol._id
}
}
}
]);
return checkOverAndSelected(Object.assign({}, state, {
redos: [],
data: makeElementSymbolActionResult.data,
actions: [...state.actions, makeElementSymbolActionResult.revertAction]
}));
}
case actionTypes.saveStyle: {
const styleActionResult = doAction(state.data, {
type: 'changeProp',
property: 'style',
value: action.data.addStyle._id,
elementId: action.params.elementId,
display: action.params.display
});
return checkOverAndSelected(Object.assign({}, state, {
data: styleActionResult.data
}));
}
case actionTypes.pbSelectElement: {
const expandedChanges = {};
let element = state.data[action.elementId];
if (element) {
while (element && element.parent) {
element = state.data[element.parent];
if (element && element.id !== 'body') {
expandedChanges[element.id] = true;
}
}
}
return checkOverAndSelected(Object.assign({}, state, {
expanded: expandedChanges,
selectedId: action.elementId
}));
}
case actionTypes.pbToggleExpandElement: {
const isExpanded = state.expanded[action.elementId] || state.userExpanded[action.elementId];
return Object.assign({}, state, {
expanded: Object.assign({}, state.expanded, {
[action.elementId]: !isExpanded
}),
userExpanded: Object.assign({}, state.userExpanded, {
[action.elementId]: !isExpanded
})
});
}
case actionTypes.pbExpandAll: {
const allExpanded = {};
forEach(state.data, (elementIt, elementId) => {
allExpanded[elementId] = true;
});
return Object.assign({}, state, {
userExpanded: allExpanded
});
}
case actionTypes.pbCollapseAll:
return Object.assign({}, state, {
expanded: {},
userExpanded: {}
});
case actionTypes.pbToggleEditing:
return Object.assign({}, state, {
editing: !state.editing
});
case actionTypes.pbSetMenuTab:
return Object.assign({}, state, {
menuTab: action.value
});
case actionTypes.pbOpenElementsMenu:
return Object.assign({}, state, {
elementsMenuOpened: true,
elementsMenuOptions: action.options
});
case actionTypes.pbCloseElementsMenu:
return Object.assign({}, state, {
elementsMenuOpened: false,
elementsMenuOptions: null,
elementsMenuSpot: null
});
case actionTypes.pbToggleCategory:
return Object.assign({}, state, {
categoriesCollapsed: Object.assign({}, state.categoriesCollapsed, {
[action.category]: !state.categoriesCollapsed[action.category]
})
});
case actionTypes.pbOverElement:
return checkOverAndSelected(Object.assign({}, state, {
overedId: action.elementId
}));
case actionTypes.pbOutElement:
if (state.overedId === action.elementId) {
return checkOverAndSelected(Object.assign({}, state, {
overedId: null
}));
}
return state;
case actionTypes.pbLinkFormDataMode:
return checkOverAndSelected(Object.assign({}, state, {
linkingFormData: true,
linkingFormDataElementId: action.elementId,
focusElementId: action.elementId
}));
case actionTypes.pbCloseLinkFormDataMode:
return checkOverAndSelected(Object.assign({}, state, {
linkingFormData: false,
linkingFormDataElementId: null,
focusElementId: null
}));
case actionTypes.pbLinkDataMode:
return checkOverAndSelected(Object.assign({}, state, {
linkingData: true,
linkingDataElementId: action.elementId,
focusElementId: action.elementId
}));
case actionTypes.pbCloseLinkDataMode:
return checkOverAndSelected(Object.assign({}, state, {
linkingData: false,
linkingDataElementId: null,
focusElementId: null
}));
default:
return state;
}
}
================================================
FILE: lib/shared/reducers/revisions.js
================================================
import actionTypes from 'actions';
const defaultState = {
data: [],
errors: null
};
export default function colorsReducer (state = defaultState, action = {}) {
switch (action.type) {
case actionTypes.getRevisions:
return Object.assign({}, state, {
data: action.data.revisions,
errors: action.errors
});
default:
return state;
}
}
================================================
FILE: lib/shared/reducers/schema-entry.js
================================================
import actionTypes from 'actions';
import parseFields from 'helpers/parse-fields';
const defaultState = {
data: {
title: '',
slug: '',
state: 'draft'
},
isSlugValid: false,
errors: 'Not found'
};
const parsableFields = ['data', 'properties'];
export default function schemaEntryReducer (state = defaultState, action = {}) {
switch (action.type) {
case actionTypes.changeSchemaEntryToDefault:
return Object.assign({}, state, defaultState);
case actionTypes.changeSchemaEntryFields:
return Object.assign({}, state, {
data: Object.assign({}, state.data, action.values)
});
case actionTypes.changeSchemaEntryProperty:
return Object.assign({}, state, {
data: Object.assign({}, state.data, {
properties: Object.assign({}, state.data.properties, {
[action.key]: action.value
})
})
});
case actionTypes.graphql:
if (action.data.schemaEntry) {
return Object.assign({}, state, {
data: Object.assign({}, state.data, parseFields(action.data.schemaEntry, parsableFields)),
errors: action.errors
});
}
return state;
case actionTypes.updateSchemaEntry:
return Object.assign({}, state, {
data: parseFields(action.data.updateSchemaEntry, parsableFields) || state.data,
errors: action.errors
});
case actionTypes.addSchemaEntry:
return Object.assign({}, state, {
data: parseFields(action.data.addSchemaEntry, parsableFields) || state.data,
errors: action.errors
});
case actionTypes.restoreSchemaEntry:
return Object.assign({}, state, {
data: parseFields(action.data.restoreSchemaEntry, parsableFields) || state.data,
errors: action.errors
});
case actionTypes.validateSchemaEntrySlug:
return Object.assign({}, state, {
isSlugValid: action.data.validateSchemaEntrySlug,
errors: action.errors
});
default:
return state;
}
}
================================================
FILE: lib/shared/reducers/schema.js
================================================
import actionTypes from 'actions';
import forEach from 'lodash.foreach';
import slugify from 'slug';
const defaultState = {
step: 0,
openedProperties: [],
data: {
type: '',
title: '',
properties: []
}
};
function getUniqueId (properties, idParam, count = 0) {
let id = idParam;
if (id === '') {
id = 'unnamed';
}
id = slugify(id).toLowerCase();
let currentId = count === 0 ? id : `${id}-${count}`;
// ensure unique
forEach(properties, property => {
if (property.id === currentId) {
currentId = getUniqueId(properties, id, count + 1);
return false;
}
});
return currentId;
}
function findPropertyBydId (properties, id) {
let result = false;
forEach(properties, (property, index) => {
if (property.id === id) {
result = {
property,
index
};
return false;
}
});
return result;
}
export default function schemaReducer (state = defaultState, action = {}) {
switch (action.type) {
case actionTypes.changeSchemaToDefault:
return Object.assign({}, state, defaultState);
case actionTypes.changeSchemaType:
return Object.assign({}, state, {
step: 1,
data: Object.assign({}, state.data, {type: action.schemaType})
});
case actionTypes.changeSchemaTitle:
return Object.assign({}, state, {
data: Object.assign({}, state.data, {title: action.title})
});
case actionTypes.schemaStepBack:
return Object.assign({}, state, {
step: state.step - 1
});
case actionTypes.schemaStepForward:
return Object.assign({}, state, {
step: state.step + 1
});
case actionTypes.schemaAddProperty: {
const newId = getUniqueId(state.data.properties, 'New Property');
return Object.assign({}, state, {
openedProperties: [...state.openedProperties, newId],
data: Object.assign({}, state.data, {
properties: [...state.data.properties, {
id: newId,
title: 'New Property',
type: 'String',
required: false
}]
})
});
}
case actionTypes.schemaToggleProperty: {
let resultState = state;
const propertyFound = findPropertyBydId(state.data.properties, action.id);
if (propertyFound) {
const openedIndex = state.openedProperties.indexOf(action.id);
if (openedIndex !== -1) {
// remove it
const newOpenedProperties = state.openedProperties.slice(0);
newOpenedProperties.splice(openedIndex, 1);
resultState = Object.assign({}, state, {
openedProperties: newOpenedProperties
});
} else {
// add it
resultState = Object.assign({}, state, {
openedProperties: [...state.openedProperties, action.id]
});
}
}
return resultState;
}
case actionTypes.schemaChangePropertySetting: {
let resultState = state;
const propertyFound = findPropertyBydId(state.data.properties, action.id);
if (propertyFound) {
const properties = state.data.properties.slice(0);
let openedProperties = state.openedProperties;
const changes = {
[action.settingId]: action.value
};
if (action.settingId === 'title') {
const newId = getUniqueId(state.data.properties, action.value);
const openedIndex = openedProperties.indexOf(action.id);
if (openedIndex !== -1) {
openedProperties = openedProperties.slice(0);
openedProperties[openedIndex] = newId;
}
changes.id = newId;
}
properties[propertyFound.index] = Object.assign(
{},
properties[propertyFound.index],
changes
);
resultState = Object.assign({}, state, {
openedProperties,
data: Object.assign({}, state.data, {
properties
})
});
}
return resultState;
}
default:
return state;
}
}
================================================
FILE: lib/shared/reducers/session.js
================================================
import actionTypes from 'actions';
const defaultState = {
userId: null
};
export default function sessionReducer (state = defaultState, action = {}) {
switch (action.type) {
case actionTypes.authenticate:
return Object.assign({}, state, {userId: action.data.userId});
default:
return state;
}
}
================================================
FILE: lib/shared/reducers/settings.js
================================================
import actionTypes from 'actions';
import forEach from 'lodash.foreach';
const defaultState = {
data: {},
errors: null
};
function parseSettings (_settings) {
const settings = {};
forEach(_settings, (setting) => {
settings[setting._id] = setting.value;
});
return settings;
}
export default function settingsReducer (state = defaultState, action = {}) {
switch (action.type) {
case actionTypes.graphql:
if (action.data.settings) {
return Object.assign({}, state, {
data: parseSettings(action.data.settings),
errors: action.errors
});
}
return state;
case actionTypes.changeSettingValue:
return Object.assign({}, state, {
data: Object.assign({}, state.data, {[action.id]: action.value}),
errors: action.errors
});
case actionTypes.saveSettings:
return Object.assign({}, state, {
data: Object.assign({}, state.data),
errors: action.errors
});
default:
return state;
}
}
================================================
FILE: lib/shared/reducers/styles.js
================================================
import actionTypes from 'actions';
import filter from 'lodash.filter';
import find from 'lodash.find';
import forEach from 'lodash.foreach';
import parseFields from 'helpers/parse-fields';
import Relate from 'relate-js';
const defaultState = {
data: [],
errors: null
};
const parsableFields = ['options', 'displayOptions'];
export default function stylesReducer (state = defaultState, action = {}) {
switch (action.type) {
case Relate.actionTypes.query:
if (action.data.styles) {
const styles = [];
forEach(action.data.styles, (style) => {
styles.push(parseFields(style, parsableFields));
});
return Object.assign({}, state, {
data: styles,
errors: action.errors
});
}
return state;
case actionTypes.saveStyle: {
const newStyle = parseFields(action.data.addStyle, parsableFields);
return Object.assign({}, state, {
data: [...state.data, newStyle],
errors: action.errors
});
}
case actionTypes.changeStyleProp: {
const data = state.data.slice(0);
const style = find(data, {_id: action.styleId});
if (action.display === 'desktop') {
Object.assign(style, {
options: Object.assign({}, style.options, {
[action.property]: action.value
})
});
} else {
Object.assign(style, {
displayOptions: Object.assign({}, style.displayOptions || {}, {
[action.display]: Object.assign(
{},
style.displayOptions && style.displayOptions[action.display] || {},
{
[action.property]: action.value
}
)
})
});
}
return Object.assign({}, state, {
data
});
}
case actionTypes.removeStyle:
return Object.assign({}, state, {
data: filter(state.data, (styleIt) => (styleIt._id !== action.data.removeStyle._id)),
errors: action.errors
});
default:
return state;
}
}
================================================
FILE: lib/shared/reducers/symbols.js
================================================
import actionTypes from 'actions';
import forEach from 'lodash.foreach';
import parseFields from 'helpers/parse-fields';
const parsableFields = ['data'];
const defaultState = {};
export default function symbolsReducer (state = defaultState, action = {}) {
switch (action.type) {
case actionTypes.graphql:
if (action.data.symbols) {
const changes = {};
forEach(action.data.symbols, (symbol) => {
if (state[symbol._id]) {
changes[symbol._id] = Object.assign({}, state[symbol._id], symbol);
} else {
changes[symbol._id] = symbol;
}
});
return Object.assign({}, state, changes);
}
return state;
case actionTypes.makeElementSymbol: {
const _id = action.data.addSymbol._id;
const data = Object.assign({}, action.data.addSymbol, {
data: JSON.parse(action.data.addSymbol.data)
});
return Object.assign({}, state, {
[_id]: Object.assign({}, state[_id] || {}, data)
});
}
case actionTypes.getSymbol: {
return Object.assign({}, state, {
[action.data.symbol._id]: Object.assign(
{},
state[action.data.symbol._id] || {},
parseFields(action.data.symbol, parsableFields)
)
});
}
default:
return state;
}
}
================================================
FILE: lib/shared/routers/admin.js
================================================
import request from 'superagent';
import Admin from 'screens/admin';
import AnalyticsSettings from 'screens/admin/screens/settings/screens/analytics';
import Colors from 'screens/admin/screens/colors';
import DataSettings from 'screens/admin/screens/settings/screens/data';
import EmailSettings from 'screens/admin/screens/settings/screens/email';
import Fonts from 'screens/admin/screens/fonts';
import GeneralSettings from 'screens/admin/screens/settings/screens/general';
import Media from 'screens/admin/screens/media';
import MediaMenu from 'screens/admin/screens/media/menu';
import Menu from 'screens/admin/screens/menus/screens/menu';
import Menus from 'screens/admin/screens/menus';
import MenusMenu from 'screens/admin/screens/menus/menu';
import Page from 'screens/admin/screens/pages/screens/page';
import Pages from 'screens/admin/screens/pages';
import PagesMenu from 'screens/admin/screens/pages/menu';
import React from 'react';
import Schema from 'screens/admin/screens/schemas/screens/schema';
import SchemaMenu from 'screens/admin/screens/schemas/screens/schema/menu';
import SchemasNew from 'screens/admin/screens/schemas/screens/new';
import SettingsMenu from 'screens/admin/screens/settings/menu';
import Users from 'screens/admin/screens/users';
import {Route, IndexRoute} from 'react-router';
function authenticate (nextState, replaceState, callback) {
if (typeof window !== 'undefined') {
request
.post('/graphql')
.set('Content-Type', 'application/json')
.set('Accept', 'application/json')
.send({
query: 'query { session }'
})
.end((error, result) => {
if (error || !result.body.data.session) {
window.location.href = '/admin/login';
} else {
callback();
}
});
} else {
callback();
}
}
export default [
];
================================================
FILE: lib/shared/routers/auth.js
================================================
import gaSend from 'helpers/ga-send';
import Auth from 'screens/auth';
import Init from 'screens/auth/screens/init';
import Login from 'screens/auth/screens/login';
import React from 'react';
import {Route} from 'react-router';
export default [
];
================================================
FILE: lib/shared/routers/public.js
================================================
// import React from 'react';
// import {Route} from 'react-router';
export default [];
================================================
FILE: lib/shared/screens/admin/components/admin/index.jsx
================================================
import cx from 'classnames';
import forEach from 'lodash.foreach';
import velocity from 'velocity-animate';
import Component from 'components/component';
import PageBuilderMenu from 'components/page-builder-menu';
import React, {PropTypes} from 'react';
import styles from './index.less';
import Menu from '../menu';
import TopBar from '../top-bar';
export default class Admin extends Component {
static propTypes = {
children: PropTypes.node,
routes: PropTypes.array.isRequired,
location: PropTypes.object.isRequired,
previewing: PropTypes.bool.isRequired,
toggleEditing: PropTypes.func.isRequired
};
getInitState () {
const {location} = this.props;
return {
build: location.query.build && true
};
}
componentWillReceiveProps (nextProps) {
const {location} = this.props;
const oldBuild = location.query.build;
const currentBuild = nextProps.location.query.build;
const config = {
duration: 800,
display: null,
easing: 'easeOutExpo'
};
if (oldBuild !== currentBuild) {
if (currentBuild) {
velocity.hook(this.refs.content, 'translateX', '0px');
velocity(this.refs.content, {translateX: '-290px'}, config);
} else {
velocity.hook(this.refs.content, 'translateX', '-290px');
velocity(this.refs.content, {translateX: '0px'}, config);
}
}
if (nextProps.previewing !== this.props.previewing) {
if (nextProps.previewing) {
velocity(this.refs.content, {top: '0px'}, config);
} else {
velocity(this.refs.content, {top: '45px'}, config);
}
}
}
render () {
const {previewing, toggleEditing} = this.props;
return (
{this.props.children}
);
}
renderMenuContent () {
const {routes} = this.props;
if (routes.length >= 2) {
let MenuTag = false;
forEach(routes, route => {
if (route.menu) {
MenuTag = route.menu;
}
});
if (MenuTag) {
return ;
}
}
}
}
================================================
FILE: lib/shared/screens/admin/components/admin/index.less
================================================
@import '~styles/sizes.less';
:global {
body {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
overflow: hidden;
font-family: 'Open Sans';
}
}
.root {
position: absolute;
top: 0; left: 0; bottom: 0; right: 0;
}
.content {
position: absolute;
top: @topMenuHeight;
left: 0;
bottom: 0;
right: 0;
}
.pageContent {
position: absolute;
top: 0;
left: @menuWidth;
right: 0;
bottom: 0;
}
.pagePreviewing {
right: -290px;
}
.build {
transform: translateX(-290px);
}
================================================
FILE: lib/shared/screens/admin/components/loading/index.jsx
================================================
import cx from 'classnames';
import React from 'react';
import Component from 'components/component';
import styles from './index.less';
export default class Loading extends Component {
render () {
return (
);
}
}
================================================
FILE: lib/shared/screens/admin/components/loading/index.less
================================================
.loading {
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
background: url('/images/admin/loading.gif') center center no-repeat;
}
================================================
FILE: lib/shared/screens/admin/components/menu/content-types.jsx
================================================
import cx from 'classnames';
import A from 'components/a';
import Button from 'components/menu-button';
import Component from 'components/component';
import React, {PropTypes} from 'react';
import styles from './content-types.less';
export default class ContentTypes extends Component {
static fragments = {
schemas: {
_id: 1,
title: 1,
type: 1
}
};
static propTypes = {
active: PropTypes.string,
onActiveClick: PropTypes.func.isRequired,
schemas: PropTypes.array.isRequired
};
render () {
const {schemas} = this.props;
return (
{schemas.map(this.renderSchema, this)}
);
}
renderSchema (schema) {
const {active, onActiveClick} = this.props;
return (
);
}
}
================================================
FILE: lib/shared/screens/admin/components/menu/content-types.less
================================================
@import '~styles/colors.less';
.contentTypes {
margin-top: 10px;
}
.header {
padding: 0 15px;
}
.text {
display: inline-block;
vertical-align: top;
text-transform: uppercase;
font-size: 12px;
line-height: 40px;
font-weight: 600;
color: @adminText;
}
.button {
float: right;
display: inline-block;
vertical-align: top;
:global i {
color: @adminText;
font-size: 16px;
line-height: 40px;
}
&:hover :global i {
color: @primarySub;
}
}
================================================
FILE: lib/shared/screens/admin/components/menu/index.js
================================================
import * as adminMenuActions from 'actions/admin-menu';
import bind from 'decorators/bind';
import forEach from 'lodash.foreach';
import Component from 'components/component';
import React, {PropTypes} from 'react';
import {bindActionCreators} from 'redux';
import {dataConnect} from 'relate-js';
import Menu from './menu';
@dataConnect(
(state) => ({
adminMenuOpened: state.adminMenu,
routes: state.router.routes,
params: state.router.params
}),
(dispatch) => bindActionCreators(adminMenuActions, dispatch),
() => ({
fragments: Menu.fragments,
mutations: {
addSchema: [{
type: 'APPEND',
field: 'schemas'
}]
}
})
)
export default class MenuContainer extends Component {
static propTypes = {
fetchData: PropTypes.func.isRequired,
adminMenuOpened: PropTypes.bool.isRequired,
openAdminMenu: PropTypes.func.isRequired,
routes: PropTypes.array.isRequired,
params: PropTypes.object.isRequired,
children: PropTypes.node
};
static defaultProps = {
schemas: []
};
static menuData = [
{
name: 'adminSettings',
label: 'General Settings',
icon: 'nc-icon-outline ui-1_preferences-container',
link: '/admin/settings'
},
'sep',
{
name: 'adminPages',
label: 'Pages',
icon: 'nc-icon-outline design_window-paragraph',
link: '/admin/pages'
},
{
name: 'adminTemplates',
label: 'Templates',
icon: 'nc-icon-outline ui-2_webpage',
link: '/admin/templates'
},
'sep',
{
name: 'adminMedia',
label: 'Media',
icon: 'nc-icon-outline media-1_album',
link: '/admin/media'
},
{
name: 'adminMenus',
label: 'Menus',
icon: 'nc-icon-outline design_bullet-list-67',
link: '/admin/menus'
},
{
name: 'adminFonts',
label: 'Fonts',
icon: 'nc-icon-outline design_text',
link: '/admin/fonts'
},
{
name: 'adminColors',
label: 'Colors',
icon: 'nc-icon-outline design_palette',
link: '/admin/colors'
},
{
name: 'adminUsers',
label: 'Users',
icon: 'nc-icon-outline users_multiple-19',
link: '/admin/users'
}
];
@bind
onActiveClick () {
this.props.openAdminMenu();
}
render () {
const {routes} = this.props;
let active = false;
if (routes.length >= 2) {
forEach(MenuContainer.menuData, (entry) => {
if (entry !== 'sep' && entry.name === routes[1].name) {
active = entry.label;
}
});
if (routes.length > 2 && routes[2].name === 'adminSchema') {
const {params} = this.props;
active = params.id;
}
}
const opened = this.props.adminMenuOpened && active && this.props.children && true;
return (
);
}
}
================================================
FILE: lib/shared/screens/admin/components/menu/menu.jsx
================================================
import cx from 'classnames';
import velocity from 'velocity-animate';
import Button from 'components/menu-button';
import Component from 'components/component';
import Scrollable from 'components/scrollable';
import React, {PropTypes} from 'react';
import {mergeFragments} from 'relate-js';
import styles from './menu.less';
import ContentTypes from './content-types';
import User from './user';
export default class Menu extends Component {
static fragments = mergeFragments(
ContentTypes.fragments,
User.fragments
);
static propTypes = {
active: PropTypes.string,
opened: PropTypes.bool,
menuData: PropTypes.array.isRequired,
schemas: PropTypes.array.isRequired,
onActiveClick: PropTypes.func.isRequired,
children: PropTypes.node
};
static defaultProps = {
opened: false
};
getInitState () {
return {
opened: this.props.opened
};
}
componentWillReceiveProps (nextProps) {
if (nextProps.opened !== this.props.opened) {
const config = {
duration: 800,
display: null,
easing: 'easeOutExpo'
};
if (nextProps.opened) {
velocity.hook(this.refs.menu, 'translateX', '0%');
velocity.hook(this.refs.content, 'translateX', '100%');
velocity(this.refs.menu, {translateX: '-100%'}, config);
velocity(this.refs.content, {translateX: '0%'}, config);
} else {
velocity.hook(this.refs.menu, 'translateX', '-100%');
velocity.hook(this.refs.content, 'translateX', '0%');
velocity(this.refs.menu, {translateX: '0%'}, config);
velocity(this.refs.content, {translateX: '100%'}, config);
}
}
}
render () {
const {menuData, schemas, active, onActiveClick, user} = this.props;
return (
{menuData.map(this.renderEntry, this)}
{this.props.children}
);
}
renderEntry (entry, key) {
let result;
if (entry === 'sep') {
result = ;
} else {
const {active, onActiveClick} = this.props;
result = (
);
}
return result;
}
}
================================================
FILE: lib/shared/screens/admin/components/menu/menu.less
================================================
@import '~styles/sizes.less';
@import '~styles/colors.less';
.root {
position: absolute;
top: 0;
left: 0;
bottom: 0;
width: @menuWidth;
border-right: 1px solid @adminBorders;
overflow: hidden;
}
.menu {
position: absolute;
top: 0;
left: 0;
right: -1px;
bottom: 80px;
transform: translateX(0%);
border-right: 1px solid @adminBorders;
z-index: 1;
}
.content {
position: absolute;
top: 0;
left: 0;
right: -1px;
bottom: 80px;
transform: translateX(100%);
}
.opened {
.menu {
transform: translateX(-100%);
}
.content {
transform: translateX(0%);
}
}
.menuContent {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0px;
overflow-y: auto;
overflow-x: hidden;
}
.sepperator {
display: block;
height: 0px;
width: 100%;
border-bottom: 1px solid @adminBorders;
margin: 0;
padding: 0;
}
================================================
FILE: lib/shared/screens/admin/components/menu/user.jsx
================================================
import bind from 'decorators/bind';
import cx from 'classnames';
import getGravatarImage from 'helpers/get-gravatar-image';
import Component from 'components/component';
import React, {PropTypes} from 'react';
import {Link} from 'react-router';
import styles from './user.less';
export default class User extends Component {
static fragments = {
user: {
_id: 1,
email: 1,
name: 1
}
};
static propTypes = {
user: PropTypes.object.isRequired
};
getInitState () {
return {
opened: false
};
}
@bind
toggleOpened () {
this.setState({
opened: !this.state.opened
});
}
render () {
const {email, name} = this.props.user || {};
const {opened} = this.state;
const url = getGravatarImage(email, 50);
return (
);
}
}
================================================
FILE: lib/shared/screens/admin/components/menu/user.less
================================================
@import '~styles/colors.less';
@height: 80px;
.root {
position: absolute;
left: 0;
bottom: 0;
right: 0;
height: @height;
overflow: hidden;
border-top: 1px solid @adminBorders;
background-color: #ffffff;
z-index: 1;
transition: height 0.2s ease-out;
}
.head {
height: @height;
cursor: pointer;
padding: 17px;
}
.opened {
height: @height + 100px;
}
.thumbnail {
display: inline-block;
vertical-align: top;
width: 46px;
height: 46px;
position: relative;
}
.blurred {
display: inline-block;
vertical-align: top;
width: 46px;
height: 46px;
border-radius: 50%;
overflow: hidden;
:global img {
width: 46px;
-webkit-filter: blur(6px);
-moz-filter: blur(6px);
-o-filter: blur(6px);
-ms-filter: blur(6px);
filter: blur(6px);
}
}
.image {
display: inline-block;
vertical-align: top;
width: 40px;
height: 40px;
border-radius: 50%;
overflow: hidden;
position: absolute;
top: 3px;
left: 3px;
:global img {
width: 40px;
}
}
.info {
display: inline-block;
vertical-align: top;
padding: 5px 13px;
}
.name {
color: @adminTextHighlight;
font-size: 15px;
}
.role {
color: @adminText;
font-size: 10px;
}
.toggle {
display: inline-block;
height: @height;
text-align: center;
position: absolute;
top: 0;
right: 0;
padding: 0 17px;
:global i {
color: @adminText;
font-size: 12px;
line-height: @height;
}
}
.actions {
padding: 0px 20px;
padding-left: 73px;
padding-bottom: 20px;
}
.action {
display: block;
text-decoration: none;
margin-bottom: 7px;
:global {
i, span {
display: inline-block;
vertical-align: top;
font-size: 12px;
color: @primary;
line-height: 20px;
}
i {
margin-right: 7px;
}
}
}
================================================
FILE: lib/shared/screens/admin/components/top-bar/actions/actions.jsx
================================================
import Component from 'components/component';
import React, {PropTypes} from 'react';
import styles from './actions.less';
import Back from './back';
import Displays from './displays';
import RightMenu from './right-menu';
import Statuses from './statuses';
export default class Actions extends Component {
static propTypes = {
location: PropTypes.object.isRequired,
display: PropTypes.string.isRequired,
changeDisplay: PropTypes.func.isRequired,
draftHasChanges: PropTypes.bool.isRequired,
state: PropTypes.string,
stateMessage: PropTypes.string,
toggleEditing: PropTypes.func.isRequired,
building: PropTypes.bool.isRequired
};
render () {
const {location, building} = this.props;
return (
{this.renderDisplay()}
{building && this.renderStatuses()}
{this.renderRightMenu()}
);
}
renderDisplay () {
const {display, changeDisplay} = this.props;
return (
);
}
renderStatuses () {
const {draftHasChanges, state, stateMessage} = this.props;
return (
);
}
renderRightMenu () {
const {toggleEditing, building} = this.props;
return (
);
}
}
================================================
FILE: lib/shared/screens/admin/components/top-bar/actions/actions.less
================================================
@import '~styles/sizes.less';
@import '~styles/colors.less';
.root {
height: @actionsHeight;
border-bottom: 1px solid @chromeBordersColor;
}
================================================
FILE: lib/shared/screens/admin/components/top-bar/actions/back.jsx
================================================
import A from 'components/a';
import Component from 'components/component';
import React, {PropTypes} from 'react';
import styles from './back.less';
export default class Back extends Component {
static propTypes = {
link: PropTypes.string.isRequired
};
static defaultProps = {
link: '#'
};
render () {
return (
);
}
}
================================================
FILE: lib/shared/screens/admin/components/top-bar/actions/back.less
================================================
@import '~styles/sizes.less';
.root {
position: relative;
display: inline-block;
vertical-align: top;
height: @actionsHeight;
}
.logo {
width: 15px;
top: 4.5px;
margin-left: 6px;
position: relative;
display: inline-block;
}
.lettering {
position: relative;
margin-left: 4px;
display: inline-block;
top: 4px;
height: 13px;
}
================================================
FILE: lib/shared/screens/admin/components/top-bar/actions/display.jsx
================================================
import bind from 'decorators/bind';
import cx from 'classnames';
import Component from 'components/component';
import React, {PropTypes} from 'react';
import styles from './display.less';
export default class DisplayButton extends Component {
static propTypes = {
onChange: PropTypes.func.isRequired,
display: PropTypes.string.isRequired,
icon: PropTypes.string.isRequired,
active: PropTypes.bool.isRequired
};
@bind
onClick () {
const {onChange, display} = this.props;
onChange(display);
}
render () {
const {icon, active} = this.props;
return (
);
}
}
================================================
FILE: lib/shared/screens/admin/components/top-bar/actions/display.less
================================================
@import '~styles/colors.less';
@import '~styles/sizes.less';
.button {
:global {
i {
width: @actionsHeight;
font-size: 13px;
line-height: @actionsHeight;
color: @chromeTextColor;
}
}
&:nth-child(2) :global i {
font-size: 11px;
}
&:nth-child(3) :global i {
font-size: 10px;
}
}
.unfocus {
}
================================================
FILE: lib/shared/screens/admin/components/top-bar/actions/displays.jsx
================================================
import cx from 'classnames';
import Component from 'components/component';
import React, {PropTypes} from 'react';
import styles from './displays.less';
import Display from './display';
export default class Displays extends Component {
static propTypes = {
display: PropTypes.string.isRequired,
disabled: PropTypes.bool.isRequired,
onChange: PropTypes.func.isRequired
};
static defaultProp = {
disabled: false
};
static displaysArr = [
{
display: 'desktop',
icon: 'nc-icon-mini tech_desktop-screen'
},
{
display: 'tablet',
icon: 'nc-icon-mini tech_tablet-button'
},
{
display: 'mobile',
icon: 'nc-icon-mini tech_mobile-button'
}
];
render () {
const {display, disabled} = this.props;
const positions = {
desktop: 0,
tablet: -25,
mobile: -50
};
const centerMenuStyle = {
left: positions[display]
};
return (
{Displays.displaysArr.map(this.renderButton, this)}
);
}
renderButton ({display, icon}) {
const {onChange} = this.props;
return (
);
}
}
================================================
FILE: lib/shared/screens/admin/components/top-bar/actions/displays.less
================================================
@import '~styles/sizes.less';
@import '~styles/colors.less';
@numberDisplays: 3;
.root {
position: absolute;
top: 0;
left: 0;
right: 0px;
height: @actionsHeight;
overflow: hidden;
}
.wrapper {
position: absolute;
left: 50%;
width: @actionsHeight;
height: @actionsHeight;
overflow: hidden;
transform: translateX(-(@actionsHeight / 2));
transition: all 0.2s ease-out;
&:hover {
width: @actionsHeight * @numberDisplays * 2;
@translate: @actionsHeight * @numberDisplays + @actionsHeight / 2;
transform: translateX(-@translate);
.slider {
transform: translateX(@actionsHeight * @numberDisplays);
}
}
}
.slider {
position: absolute;
top: 0;
left: 0;
width: @actionsHeight * @numberDisplays;
transition: all 0.2s ease-out;
transform: translateX(0);
}
================================================
FILE: lib/shared/screens/admin/components/top-bar/actions/index.js
================================================
import * as displayActions from 'actions/display';
import * as draftActions from 'actions/draft';
import bind from 'decorators/bind';
import Component from 'components/component';
import React, {PropTypes} from 'react';
import {toggleEditing} from 'actions/page-builder';
import {bindActionCreators} from 'redux';
import {dataConnect} from 'relate-js';
import Actions from './actions';
// import * as pageActions from 'actions/page';
@dataConnect(
(state) => ({
draftID: state.router.params.id,
display: state.display,
location: state.router.location,
draftHasChanges: state.pageBuilder.actions.length > 0,
draftActionsNumb: state.pageBuilder.actions.length,
building: state.router.location.query.build
}),
(dispatch) => bindActionCreators({
...displayActions,
...draftActions,
toggleEditing
}, dispatch),
(props) => {
let result = {};
if (props.draftID) {
result = {
fragments: {
draft: {
_id: {
_id: 1,
_userId: 1
},
__v: 1
}
},
variablesTypes: {
draft: {
id: 'ID!'
}
},
initialVariables: {
draft: {
id: props.draftID
}
}
};
}
return result;
}
)
export default class ActionsContainer extends Component {
static propTypes = {
saveDraft: PropTypes.object.isRequired,
dropDraft: PropTypes.object.isRequired,
pageActions: PropTypes.object.isRequired,
pageBuilderActions: PropTypes.object.isRequired,
activePanelType: PropTypes.string.isRequired,
changeDisplay: PropTypes.func.isRequired,
draftActionsNumb: PropTypes.number.isRequired,
draftID: PropTypes.string
};
getInitState () {
this.preventNavigationBind = ::this.preventNavigation;
if (this.isClient()) {
window.addEventListener('beforeunload', this.preventNavigationBind);
}
return {
state: null,
stateMessage: ''
};
}
componentWillReceiveProps (nextProps) {
if (nextProps.draftActionsNumb !== this.props.draftActionsNumb &&
nextProps.draftID === this.props.draftID &&
nextProps.building) {
clearTimeout(this.saveTimeout);
this.saveTimeout = setTimeout(this.autosave, 2000);
}
}
componentWillUnmount () {
this.saveDraft();
window.removeEventListener('beforeunload', this.preventNavigationBind);
}
preventNavigation (event) {
if (this.saveTimeout) {
const confirmationMessage = 'Your draft has not been saved yet!';
event.returnValue = confirmationMessage;
return confirmationMessage;
}
}
saveDraft () {
if (this.saveTimeout) {
clearTimeout(this.saveTimeout);
this.saveTimeout = false;
this.props.saveDraft();
}
}
@bind
async autosave () {
if (this.props.draftID) {
this.setState({
state: 'loading',
stateMessage: 'Auto saving draft'
});
clearTimeout(this.saveTimeout);
this.saveTimeout = false;
try {
await this.props.saveDraft();
this.setState({
state: 'success',
stateMessage: 'Autosave successful'
});
this.successTimeout = setTimeout(this.outSuccess, 2000);
} catch (err) {
this.setState({
state: 'error',
stateMessage: 'Error auto saving draft'
});
}
}
}
// async savePage (event, publish = false) {
// event.preventDefault();
// event.stopPropagation();
// clearTimeout(this.successTimeout);
//
// this.setState({
// state: 'loading',
// stateMessage: 'Saving page'
// });
//
// try {
// await this.props.pageActions.savePageFromDraft(publish);
// this.setState({
// state: 'success',
// stateMessage: 'Page saved successfully'
// });
// this.successTimeout = setTimeout(::this.outSuccess, 2000);
// } catch (err) {
// this.setState({
// state: 'error',
// stateMessage: 'Error saving page'
// });
// }
// }
//
// publishPage (event) {
// this.savePage(event, true);
// }
//
// async fetchCurrent (event) {
// if (event && event.preventDefault) {
// event.preventDefault();
// }
// clearTimeout(this.successTimeout);
//
// this.setState({
// state: 'loading',
// stateMessage: 'Dropping draft changes'
// });
//
// try {
// await this.props.draftActions.dropDraft(this.props.draft._id._id);
// this.setState({
// state: 'success',
// stateMessage: 'Draft dropped successfully'
// });
// this.successTimeout = setTimeout(::this.outSuccess, 2000);
// } catch (err) {
// this.setState({
// state: 'error',
// stateMessage: 'Error dropping draft'
// });
// }
// }
// async onRestore (__v) {
// this.setState({
// state: 'loading',
// stateMessage: 'Restoring revisions'
// });
//
// try {
// const page = await this.props.pageActions.restorePage(this.props.page._id, __v);
//
// this.setState({
// state: 'success',
// stateMessage: 'Revisions restored successfully'
// });
//
// history.pushState({}, '', `/admin/page/${page.restorePage._id}`);
// this.successTimeout = setTimeout(::this.outSuccess, 3000);
// } catch (err) {
// this.setState({
// state: 'error',
// stateMessage: 'Error restoring revisions'
// });
// }
// }
@bind
outSuccess () {
if (this.state.state === 'success') {
this.setState({
state: null
});
}
}
// previewToggle () {
// this.props.pageBuilderActions.toggleEditing();
// }
//
// hasRevisions () {
// return this.props.page && this.props.page.__v > 0;
// }
//
// isPublished () {
// return this.props.page && this.props.page.state === 'published';
// }
render () {
return (
);
}
}
================================================
FILE: lib/shared/screens/admin/components/top-bar/actions/right-menu.jsx
================================================
import cx from 'classnames';
import Component from 'components/component';
import React, {PropTypes} from 'react';
import styles from './right-menu.less';
export default class RightMenu extends Component {
static propTypes = {
toggleEditing: PropTypes.func.isRequired,
building: PropTypes.bool.isRequired
};
render () {
const {toggleEditing, building} = this.props;
return (
);
}
}
================================================
FILE: lib/shared/screens/admin/components/top-bar/actions/right-menu.less
================================================
@import '~styles/sizes.less';
@import '~styles/colors.less';
.root {
float: right;
position: relative;
padding: 0 6px;
}
.disabled {
opacity: 0.3;
&:after {
content: '';
display: inline-block;
position: absolute;
top: 0; left: 0; right: 0; bottom: 0;
background-color: transparent;
}
}
.button {
display: inline-block;
vertical-align: top;
margin-left: 10px;
}
.iconButton {
line-height: @actionsHeight;
height: @actionsHeight;
:global i {
width: @actionsHeight;
font-size: 12px;
line-height: @actionsHeight;
color: @chromeTextColor;
}
:hover :global i {
color: @chromeTextColorHighlight;
}
}
.textButton {
margin-top: 4px;
margin-left: 20px;
line-height: @actionsHeight - 9;
height: @actionsHeight - 9;
color: @chromeTextColor;
font-size: 9px;
text-transform: uppercase;
}
.primaryButton {
background-color: @primary;
color: #ffffff;
border-radius: 3px;
padding: 0 15px;
&:hover{
background-color: @primaryDarker;
}
}
================================================
FILE: lib/shared/screens/admin/components/top-bar/actions/statuses.jsx
================================================
import cx from 'classnames';
import Animate from 'components/animate';
import Component from 'components/component';
import Spinner from 'components/spinner';
import React, {PropTypes} from 'react';
import styles from './statuses.less';
export default class Statuses extends Component {
static propTypes = {
state: PropTypes.string,
stateMessage: PropTypes.string,
behindVersion: PropTypes.bool.isRequired,
draftHasChanges: PropTypes.bool.isRequired
};
render () {
const {state, behindVersion, draftHasChanges} = this.props;
let result;
if (state) {
result = this.renderState();
} else if (behindVersion) {
result = this.renderBehind();
} else if (draftHasChanges) {
result = this.renderEditing();
} else {
result = this.renderPublished();
}
return result;
}
renderState () {
const {state, stateMessage} = this.props;
let result;
if (state === 'loading') {
result = (
{stateMessage}
);
} else if (state === 'success') {
result = (
{stateMessage}
);
} else if (state === 'error') {
result = (
{stateMessage}
);
}
return result;
}
renderBehind () {
return (
Your draft is behind current revision -
);
}
renderEditing () {
return (
Editing your draft -
);
}
renderPublished () {
return (
Seeing published version
);
}
}
================================================
FILE: lib/shared/screens/admin/components/top-bar/actions/statuses.less
================================================
@import '~styles/sizes.less';
@import '~styles/colors.less';
.root {
display: inline-block;
vertical-align: top;
position: relative;
height: @actionsHeight;
padding: 0 16px;
line-height: @actionsHeight;
}
.text {
color: @primary;
font-size: 9px;
}
.button {
color: @chromeTextColor;
font-size: 9px;
&:hover {
color: @chromeTextColorHighlight;
}
}
.state {
display: inline-block;
vertical-align: top;
padding: 0 16px;
line-height: @actionsHeight;
height: @actionsHeight;
:global {
i, span {
display: inline-block;
vertical-align: top;
line-height: @actionsHeight;
}
i {
font-size: 20px;
}
span {
margin-left: 7px;
color: @primary;
font-size: 9px;
}
}
}
.spinner {
margin-top: 5px;
}
.success {
:global {
i, span {
color: @success;
}
}
}
.error {
:global {
i, span {
color: @alert;
}
}
}
================================================
FILE: lib/shared/screens/admin/components/top-bar/index.jsx
================================================
import velocity from 'velocity-animate';
import Component from 'components/component';
import React, {PropTypes} from 'react';
import styles from './index.less';
import Actions from './actions';
import Tabs from './tabs';
export default class TopBar extends Component {
static propTypes = {
previewing: PropTypes.bool.isRequired,
toggleEditing: PropTypes.func.isRequired
};
componentWillReceiveProps (nextProps) {
const config = {
duration: 800,
display: null,
easing: 'easeOutExpo'
};
if (nextProps.previewing !== this.props.previewing) {
if (nextProps.previewing) {
velocity(this.refs.content, {translateY: '-45px'}, config);
velocity(this.refs.preview, {translateX: '-110px'}, config);
} else {
velocity(this.refs.content, {translateY: '0'}, config);
velocity(this.refs.preview, {translateX: '-0'}, config);
}
}
}
render () {
const {toggleEditing} = this.props;
return (
);
}
}
================================================
FILE: lib/shared/screens/admin/components/top-bar/index.less
================================================
@import '~styles/colors.less';
@import '~styles/sizes.less';
.root {
position: absolute;
left: 0px; right: 0; top: 0;
height: @topMenuHeight;
background-color: @chromeBackgroundColor;
border-bottom: 1px solid @chromeBordersColor;
z-index: 2;
}
.closePreview {
position: absolute;
display: inline-block;
background-color: @chromeBackgroundColor;
border-radius: 3px;
color: @chromeTextColor;
left: 100%;
top: 5px;
font-size: 11px;
text-transform: uppercase;
padding: 5px 10px;
z-index: 1000;
white-space: nowrap;
cursor: pointer;
}
================================================
FILE: lib/shared/screens/admin/components/top-bar/tabs/index.js
================================================
import * as tabsActions from 'actions/tabs';
import Component from 'components/component';
import React, {PropTypes} from 'react';
import {bindActionCreators} from 'redux';
import {dataConnect} from 'relate-js';
import Tabs from './tabs';
@dataConnect(
(state) => ({
pathname: state.router.location.pathname
}),
(dispatch) => bindActionCreators(tabsActions, dispatch),
() => ({
fragments: Tabs.fragments,
mutations: {
addTab: [{
type: 'APPEND',
field: 'tabs'
}]
}
})
)
export default class TabsContainer extends Component {
static propTypes = {
fetchData: PropTypes.func.isRequired,
tabs: PropTypes.array.isRequired,
removeTab: PropTypes.func.isRequired,
pathname: PropTypes.string.isRequired
};
static defaultProps = {
tabs: []
};
render () {
const {tabs, removeTab, pathname} = this.props;
return (
);
}
}
================================================
FILE: lib/shared/screens/admin/components/top-bar/tabs/tab.jsx
================================================
import bind from 'decorators/bind';
import cx from 'classnames';
import Component from 'components/component';
import React, {PropTypes} from 'react';
import {Link} from 'react-router';
import styles from './tab.less';
export default class Tab extends Component {
static fragments = {
tab: {
_id: 1,
type: 1,
item: {
_id: 1,
title: 1
}
}
};
static propTypes = {
activePanelType: PropTypes.string,
tab: PropTypes.object,
tabsCount: PropTypes.number,
removeTab: PropTypes.func,
pathname: PropTypes.string.isRequired
};
@bind
onCloseTab (event) {
event.preventDefault();
event.stopPropagation();
const {tab = {}, pathname, removeTab} = this.props;
const to = this.getLink();
const active = pathname === to;
removeTab(tab._id, active && to);
}
getLink () {
const {tab = {}} = this.props;
const {item = {}} = tab;
let to;
switch (tab.type) {
case 'page':
to = `/admin/pages/${item._id}`;
break;
case 'schema':
to = `/admin/schemas/${item._id}`;
break;
default:
to = '#';
}
return to;
}
render () {
const {tab = {}, pathname} = this.props;
const {item = {}} = tab;
const to = this.getLink();
const active = pathname === to;
const deduct = 35 / this.props.tabsCount;
const style = {
maxWidth: `calc(${100 / this.props.tabsCount}% - ${deduct}px)`
};
return (
{item.title}
);
}
}
================================================
FILE: lib/shared/screens/admin/components/top-bar/tabs/tab.less
================================================
@import '~styles/colors.less';
@import '~styles/sizes.less';
.tab {
height: @tabsHeight;
border-right: 1px solid @chromeBordersColor;
display: inline-block;
vertical-align: top;
text-decoration: none;
color: @chromeTextColor;
font-size: 10px;
line-height: @tabsHeight;
width: 200px;
margin: 0 -1px;
padding: 0 7.5px;
padding-right: 30px;
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
position: relative;
i {
vertical-align: top;
font-size: 11px;
line-height: @tabsHeight;
}
&:hover{
color: @chromeTextColorHighlight;
}
}
.active {
z-index: 1;
background-color: #3f4249;
color: @chromeTextColorHighlight;
}
.close {
position: absolute;
width: 13px;
height: 13px;
border-radius: 9px;
text-align: center;
top: 4px;
right: 7px;
i {
font-size: 6px;
line-height: 13px;
vertical-align: top;
color: #999999;
}
&:hover {
background-color: #DD6050;
i {
color: #ffffff;
}
}
}
================================================
FILE: lib/shared/screens/admin/components/top-bar/tabs/tabs.jsx
================================================
import Component from 'components/component';
import React, {PropTypes} from 'react';
import styles from './tabs.less';
import Tab from './tab';
export default class Tabs extends Component {
static fragments = {
tabs: Tab.fragments.tab
};
static propTypes = {
tabs: PropTypes.array.isRequired,
removeTab: PropTypes.func.isRequired,
pathname: PropTypes.string.isRequired
};
render () {
return (
{this.props.tabs.map(this.renderTab, this)}
);
}
renderTab (tab, key) {
const {removeTab, pathname} = this.props;
return (
);
}
}
================================================
FILE: lib/shared/screens/admin/components/top-bar/tabs/tabs.less
================================================
@import '~styles/sizes.less';
@import '~styles/colors.less';
.root {
height: @tabsHeight;
}
.addButton {
height: @tabsHeight;
display: inline-block;
vertical-align: top;
text-decoration: none;
color: @chromeTextColor;
padding: 0 8px;
:global i {
font-size: 8px;
line-height: @tabsHeight;
}
&:hover{
color: @chromeTextColorHighlight;
}
}
================================================
FILE: lib/shared/screens/admin/index.js
================================================
import 'styles/normalize.less';
import 'styles/nucleo/index.less';
import Component from 'components/component';
import React, {PropTypes} from 'react';
import {toggleEditing} from 'actions/page-builder';
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
import {rootDataConnect} from 'relate-js';
import Admin from './components/admin';
@rootDataConnect()
@connect(
(state) => ({
previewing: !state.pageBuilder.editing
}),
(dispatch) => bindActionCreators({toggleEditing}, dispatch)
)
export default class AdminContainer extends Component {
static propTypes = {
children: PropTypes.node.isRequired
};
render () {
return (
{this.props.children}
);
}
}
================================================
FILE: lib/shared/screens/admin/screens/colors/components/colors.jsx
================================================
import Component from 'components/component';
import Content from 'components/content';
import ContentHeader from 'components/content-header';
import ContentHeaderActions from 'components/content-header-actions';
import ContentNew from 'components/content-new';
import ContentSearch from 'components/content-search';
import React, {PropTypes} from 'react';
import List from './list';
export default class Colors extends Component {
static fragments = List.fragments;
static propTypes = {
colors: PropTypes.array.isRequired,
search: PropTypes.string.isRequired,
searchChange: PropTypes.func.isRequired,
duplicateColor: PropTypes.func.isRequired,
removeColor: PropTypes.func.isRequired
};
render () {
const {colors, search, searchChange, duplicateColor, removeColor} = this.props;
return (
Add new color
);
}
}
================================================
FILE: lib/shared/screens/admin/screens/colors/components/entry.jsx
================================================
import cx from 'classnames';
import Component from 'components/component';
import React, {PropTypes} from 'react';
import styles from './entry.less';
export default class Entry extends Component {
static fragments = {
color: {
_id: 1,
label: 1,
value: 1
}
};
static propTypes = {
color: PropTypes.object.isRequired,
duplicateColor: PropTypes.func.isRequired,
removeColor: PropTypes.func.isRequired
};
duplicate () {
const {duplicateColor, color} = this.props;
duplicateColor(color._id);
}
remove () {
const {removeColor, color} = this.props;
removeColor(color._id);
}
render () {
const {color} = this.props;
const colorStyle = {
backgroundColor: color.value
};
return (
{color.label}
{color.value}
);
}
}
================================================
FILE: lib/shared/screens/admin/screens/colors/components/entry.less
================================================
@import '~styles/colors.less';
.root {
width: 21.25%;
margin-right: 5%;
display: inline-block;
vertical-align: top;
margin-bottom: 60px;
border: 1px solid transparent;
}
@media screen and (min-width: 1501px) {
.root {
width: 16%;
&:nth-child(5n+5) {
margin-right: 0;
}
}
}
@media screen and (max-width: 1500px) and (min-width: 1101px) {
.root {
&:nth-child(4n+4) {
margin-right: 0;
}
}
}
@media screen and (max-width: 1100px) and (min-width: 851px) {
.root {
width: 30%;
&:nth-child(3n+3) {
margin-right: 0;
}
}
}
@media screen and (max-width: 850px) and (min-width: 751px) {
.root {
width: 45%;
margin-right: 10%;
&:nth-child(2n+2) {
margin-right: 0;
}
}
}
@media screen and (max-width: 750px) {
.root {
width: 100%;
margin-right: 0%;
}
}
.color {
height: 125px;
margin: -1px;
margin-bottom: 0;
position: relative;
&.white:before {
content: '';
display: block;
position: absolute;
top: 0; bottom: 0;
left: 0; right: 0;
border: 1px solid @adminBorders;
z-index: 1;
}
}
.info {
padding: 15px;
height: 65px;
text-align: center;
}
.title {
color: @adminTextHighlight;
font-size: 16px;
line-height: 19px;
}
.value {
color: @adminText;
font-size: 14px;
font-weight: 300;
text-transform: uppercase;
}
.actions {
visibility: hidden;
border-top: 1px solid @adminBorders;
}
.button {
height: 39px;
line-height: 39px;
font-size: 12px;
text-transform: uppercase;
width: 50%;
border-right: 1px solid @adminBorders;
color: @adminText;
}
.remove {
color: @alert;
}
.root:hover {
border: 1px solid @adminBorders;
.actions {
visibility: visible;
}
}
================================================
FILE: lib/shared/screens/admin/screens/colors/components/list.jsx
================================================
import Component from 'components/component';
import React, {PropTypes} from 'react';
import {mergeFragments} from 'relax-fragments';
import Entry from './entry';
export default class List extends Component {
static fragments = mergeFragments({
colors: Entry.fragments.color
}, {
colors: {
_id: 1,
label: 1
}
});
static propTypes = {
colors: PropTypes.array.isRequired,
search: PropTypes.string,
duplicateColor: PropTypes.func.isRequired,
removeColor: PropTypes.func.isRequired
};
render () {
const {colors} = this.props;
return (
{colors.map(this.renderEntry, this)}
);
}
renderEntry (color) {
const {search, duplicateColor, removeColor} = this.props;
let valid = true;
if (search) {
valid = color.label.toLowerCase().indexOf(search.toLowerCase()) >= 0;
}
if (valid) {
return (
);
}
}
}
================================================
FILE: lib/shared/screens/admin/screens/colors/index.js
================================================
import * as colorsActions from 'actions/colors';
import Component from 'components/component';
import React, {PropTypes} from 'react';
import {bindActionCreators} from 'redux';
import {dataConnect} from 'relate-js';
import Colors from './components/colors.jsx';
@dataConnect(
null,
(dispatch) => bindActionCreators(colorsActions, dispatch),
() => ({
fragments: Colors.fragments,
mutations: {
duplicateColor: [{
type: 'APPEND',
field: 'colors'
}],
addColor: [{
type: 'APPEND',
field: 'colors'
}]
}
})
)
export default class ColorsContainer extends Component {
static propTypes = {
colors: PropTypes.array.isRequired,
duplicateColor: PropTypes.func.isRequired,
removeColor: PropTypes.func.isRequired
};
static defaultProps = {
colors: []
};
getInitState () {
return {
search: ''
};
}
searchChange (search) {
this.setState({
search
});
}
render () {
const {colors, duplicateColor, removeColor} = this.props;
return (
);
}
}
================================================
FILE: lib/shared/screens/admin/screens/fonts/components/entry.jsx
================================================
import cx from 'classnames';
import utils from 'helpers/utils';
import Component from 'components/component';
import React, {PropTypes} from 'react';
import styles from './entry.less';
export default class Entry extends Component {
static propTypes = {
family: PropTypes.string.isRequired,
fvd: PropTypes.string.isRequired,
text: PropTypes.string.isRequired,
display: PropTypes.oneOf(['grid', 'list']).isRequired
};
render () {
const {family, fvd, text, display} = this.props;
const style = {
fontFamily: family
};
utils.processFVD(style, fvd);
return (
{text}
{utils.filterFontFamily(family)}
{utils.filterFVD(fvd)}
);
}
}
================================================
FILE: lib/shared/screens/admin/screens/fonts/components/entry.less
================================================
@import '~styles/colors.less';
.root {
width: 21.25%;
margin-right: 5%;
display: inline-block;
vertical-align: top;
margin-bottom: 60px;
border: 1px solid transparent;
}
@media screen and (min-width: 1501px) {
.grid.root {
width: 12.5%;
&:nth-child(6n+6) {
margin-right: 0;
}
}
}
@media screen and (max-width: 1500px) and (min-width: 1301px) {
.grid.root {
width: 16%;
&:nth-child(5n+5) {
margin-right: 0;
}
}
}
@media screen and (max-width: 1300px) and (min-width: 1051px) {
.grid.root {
&:nth-child(4n+4) {
margin-right: 0;
}
}
}
@media screen and (max-width: 1050px) and (min-width: 851px) {
.grid.root {
width: 30%;
&:nth-child(3n+3) {
margin-right: 0;
}
}
}
@media screen and (max-width: 850px) and (min-width: 751px) {
.grid.root {
width: 45%;
margin-right: 10%;
&:nth-child(2n+2) {
margin-right: 0;
}
}
}
@media screen and (max-width: 750px) {
.grid.root {
width: 100%;
margin-right: 0%;
}
}
.text {
margin-top: 10px;
line-height: 125px;
font-size: 90px;
text-align: center;
color: @adminTextHighlight;
overflow: hidden;
white-space: nowrap;
text-overflow: clip;
}
.info {
padding: 15px;
height: 65px;
text-align: center;
}
.title {
color: @adminText;
font-size: 16px;
line-height: 19px;
}
.value {
color: @adminText;
font-size: 14px;
font-weight: 300;
text-transform: uppercase;
}
.list {
width: 100%;
margin-right: 0%;
.text {
text-align: left;
}
.info {
padding: 15px 0;
text-align: left;
}
}
================================================
FILE: lib/shared/screens/admin/screens/fonts/components/fonts.jsx
================================================
import Component from 'components/component';
import Content from 'components/content';
import ContentDisplays from 'components/content-displays';
import ContentHeader from 'components/content-header';
import ContentHeaderActions from 'components/content-header-actions';
import ContentNew from 'components/content-new';
import Modal from 'components/modal';
import React, {PropTypes} from 'react';
import List from './list';
import Manage from './manage';
import PreviewText from './preview-text';
export default class Fonts extends Component {
static fragments = List.fragments;
static propTypes = {
fonts: PropTypes.object.isRequired,
previewText: PropTypes.string.isRequired,
changePreviewText: PropTypes.string.isRequired,
changeDisplay: PropTypes.func.isRequired,
display: PropTypes.string.isRequired,
manage: PropTypes.bool.isRequired,
openManage: PropTypes.func.isRequired,
closeManage: PropTypes.func.isRequired,
fontsActions: PropTypes.object.isRequired
};
render () {
const {fonts, previewText, changePreviewText, changeDisplay, display, openManage} = this.props;
return (
Manage fonts
{this.renderManage()}
);
}
renderManage () {
const {manage, closeManage, fonts, fontsActions} = this.props;
if (manage) {
return (
);
}
}
}
================================================
FILE: lib/shared/screens/admin/screens/fonts/components/list.jsx
================================================
import forEach from 'lodash.foreach';
import Component from 'components/component';
import React, {PropTypes} from 'react';
import Entry from './entry';
export default class List extends Component {
static propTypes = {
fonts: PropTypes.object.isRequired,
previewText: PropTypes.string.isRequired,
display: PropTypes.oneOf(['grid', 'list']).isRequired
};
render () {
const {fonts, previewText, display} = this.props;
const list = [];
let result;
if (fonts.fonts) {
forEach(fonts.fonts, (variants, family) => {
variants.map((fvd, ind) => {
const key = (family + fvd).replace(/ /g, '_');
list.push(
);
}, this);
});
}
if (list.length > 0) {
result = (
{list}
);
} else {
result = (
No fonts yet
);
}
return result;
}
}
================================================
FILE: lib/shared/screens/admin/screens/fonts/components/manage/index.js
================================================
import Component from 'components/component';
import React from 'react';
import Manage from './manage';
const tabs = [
{
title: 'Google Fonts',
lib: 'google',
placeholder: 'Google Fonts Link'
},
{
title: 'Typekit',
lib: 'typekit',
placeholder: 'Typekit kit id'
},
{
title: 'Fonts.com',
lib: 'monotype',
placeholder: 'Fonts.com project id'
},
{
title: 'Font Deck',
lib: 'fontdeck',
placeholder: 'Fontdeck project id'
},
{
title: 'Custom Fonts',
lib: 'custom'
}
];
export default class ManageContainer extends Component {
getInitState () {
return {
tab: 0
};
}
changeTab (tab) {
this.setState({
tab
});
}
render () {
return (
);
}
}
================================================
FILE: lib/shared/screens/admin/screens/fonts/components/manage/manage.jsx
================================================
import cx from 'classnames';
import Component from 'components/component';
import ModalInput from 'components/modal-input';
import React, {PropTypes} from 'react';
import styles from './manage.less';
export default class Manage extends Component {
static propTypes = {
tabs: PropTypes.array.isRequired,
tab: PropTypes.number.isRequired,
changeTab: PropTypes.func.isRequired,
fonts: PropTypes.object.isRequired,
fontsActions: PropTypes.object.isRequired,
closeManage: PropTypes.func.isRequired
};
changeTab (tab) {
this.props.changeTab(tab);
}
render () {
const {tabs, closeManage} = this.props;
return (
{tabs.map(this.renderTabButton, this)}
{this.renderContent()}
);
}
renderTabButton (tabButton, index) {
const {tab} = this.props;
return (
);
}
renderContent () {
const {tab, tabs, fonts, fontsActions} = this.props;
const currentTab = tabs[tab];
let result;
if (currentTab.lib !== 'custom') {
const lib = currentTab.lib;
const input = fonts.input[lib];
result = (
);
} else {
result = (
custom
);
}
return result;
}
}
================================================
FILE: lib/shared/screens/admin/screens/fonts/components/manage/manage.less
================================================
@import '~styles/colors.less';
.root {
padding: 30px;
padding-top: 5px;
}
.tabs {
text-align: center;
}
.tab {
position: relative;
font-size: 16px;
line-height: 39px;
color: @adminText;
margin-bottom: 15px;
display: inline-block;
margin-right: 55px;
font-weight: 600;
&:hover, &.active {
color: @primary;
}
&.active {
&:after {
content: '';
position: absolute;
bottom: 0;
left: 0;
right: 0;
height: 0;
border-bottom: 1px solid @primary;
}
}
&:last-child {
margin-right: 0;
}
}
.inputArea {
padding: 0 70px;
padding-top: 50px;
}
.content {
text-align: center;
}
.done {
text-transform: uppercase;
font-size: 16px;
color: @primary;
text-align: center;
margin-top: 30px;
}
================================================
FILE: lib/shared/screens/admin/screens/fonts/components/preview-text.jsx
================================================
import Component from 'components/component';
import React, {PropTypes} from 'react';
import styles from './preview-text.less';
export default class PreviewText extends Component {
static propTypes = {
value: PropTypes.string.isRequired,
onChange: PropTypes.func.isRequired
};
onChange (event) {
this.props.onChange(event.target.value);
}
render () {
const {value} = this.props;
return (
);
}
}
================================================
FILE: lib/shared/screens/admin/screens/fonts/components/preview-text.less
================================================
@import '~styles/colors.less';
.root {
display: inline-block;
vertical-align: top;
color: @adminText;
font-size: 20px;
line-height: 30px;
font-weight: 300;
background-color: transparent;
border: 0;
outline: 0;
padding: 0;
margin: 0;
width: 250px;
&::-webkit-input-placeholder {
color: @adminTextSub;
}
&:-moz-placeholder {
color: @adminTextSub;
}
&::-moz-placeholder {
color: @adminTextSub;
}
&:-ms-input-placeholder {
color: @adminTextSub;
}
&::-ms-input-placeholder {
color: @adminTextSub;
}
&:placeholder-shown {
color: @adminTextSub;
}
}
================================================
FILE: lib/shared/screens/admin/screens/fonts/index.js
================================================
import * as fontsActions from 'actions/fonts';
import {dataConnect} from 'relate-js';
import Component from 'components/component';
import React, {PropTypes} from 'react';
import {bindActionCreators} from 'redux';
import Fonts from './components/fonts.jsx';
@dataConnect(
(state) => ({
fonts: state.fonts.data
}),
(dispatch) => ({
fontsActions: bindActionCreators(fontsActions, dispatch)
}),
() => ({
fragments: {
settings: {
_id: 1,
value: 1
}
},
variablesTypes: {
settings: {
ids: '[String]!'
}
},
initialVariables: {
settings: {
ids: ['fonts']
}
}
})
)
export default class FontsContainer extends Component {
static propTypes = {
fonts: PropTypes.object.isRequired
};
getInitState () {
return {
previewText: '',
display: 'grid',
manage: false
};
}
changeDisplay (display) {
this.setState({
display
});
}
changePreviewText (value) {
this.setState({
previewText: value
});
}
openManage () {
this.setState({
manage: true
});
}
closeManage () {
this.setState({
manage: false
});
}
render () {
return (
);
}
}
================================================
FILE: lib/shared/screens/admin/screens/media/components/entry.jsx
================================================
import bind from 'decorators/bind';
import cx from 'classnames';
import moment from 'moment';
import Component from 'components/component';
import MediaItemPreview from 'components/media-item-preview';
import React, {PropTypes} from 'react';
import {mergeFragments} from 'relate-js';
import styles from './entry.less';
export default class MediaEntry extends Component {
static fragments = mergeFragments({
mediaItem: {
_id: 1,
name: 1,
date: 1,
size: 1,
dimension: {
width: 1,
height: 1
}
}
}, MediaItemPreview.fragments);
static propTypes = {
onClick: PropTypes.func.isRequired,
mediaItem: PropTypes.object.isRequired,
selected: PropTypes.bool.isRequired,
display: PropTypes.string.isRequired
};
@bind
onClick () {
const {onClick, mediaItem} = this.props;
onClick(mediaItem._id);
}
render () {
const {mediaItem, selected, display} = this.props;
const momentDate = moment(mediaItem.date);
const sizes = display === 'list' ? {width: 250, height: 125} : {width: 300, height: 150};
return (
{mediaItem.name}
{momentDate.fromNow()}
{this.renderExtraInfo()}
);
}
renderExtraInfo () {
const {display, mediaItem} = this.props;
if (display === 'list') {
const {size, dimension} = mediaItem;
return (
{size &&
{size}
}
{dimension &&
{`${dimension.width}x${dimension.height}`}
}
);
}
}
}
================================================
FILE: lib/shared/screens/admin/screens/media/components/entry.less
================================================
@import '~styles/colors.less';
.grid {
display: inline-block;
vertical-align: top;
width: 21.25%;
margin-right: 5%;
margin-bottom: 60px;
border: 1px solid transparent;
cursor: pointer;
}
@media screen and (min-width: 1501px) {
.grid {
width: 16%;
&:nth-child(5n+5) {
margin-right: 0;
}
}
}
@media screen and (max-width: 1500px) and (min-width: 1101px) {
.grid {
&:nth-child(4n+4) {
margin-right: 0;
}
}
}
@media screen and (max-width: 1100px) and (min-width: 851px) {
.grid {
width: 30%;
&:nth-child(3n+3) {
margin-right: 0;
}
}
}
@media screen and (max-width: 850px) and (min-width: 751px) {
.grid {
width: 45%;
margin-right: 10%;
&:nth-child(2n+2) {
margin-right: 0;
}
}
}
@media screen and (max-width: 750px) {
.grid {
width: 100%;
margin-right: 0%;
}
}
.preview {
width: 100%;
height: 125px;
background-color: @adminBorders;
}
.info {
padding: 15px;
height: 65px;
text-align: center;
}
.title {
color: @adminTextHighlight;
font-size: 16px;
line-height: 19px;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.date {
color: @adminText;
font-size: 12px;
font-weight: 300;
text-transform: uppercase;
}
.extras {
margin-top: 10px;
}
.extra {
color: @adminText;
font-size: 12px;
}
.grid:hover, .list:hover {
border: 1px solid @adminBorders;
}
.grid.selected, .list.selected {
border: 1px solid @primary;
}
.list {
display: block;
margin-bottom: 20px;
border: 1px solid transparent;
cursor: pointer;
.preview {
display: table-cell;
vertical-align: top;
width: 300px;
height: 150px;
}
.info {
display: table-cell;
vertical-align: middle;
text-align: left;
padding: 10px 30px;
}
.title {
font-size: 18px;
line-height: 24px;
}
}
================================================
FILE: lib/shared/screens/admin/screens/media/components/list.jsx
================================================
import Component from 'components/component';
import React, {PropTypes} from 'react';
import Entry from './entry';
export default class MediaList extends Component {
static fragments = {
media: Entry.fragments.mediaItem
};
static propTypes = {
media: PropTypes.array.isRequired,
toggleMediaSelection: PropTypes.func.isRequired,
selected: PropTypes.array.isRequired,
display: PropTypes.string.isRequired
};
render () {
const {media} = this.props;
return (
{media.map(this.renderEntry, this)}
);
}
renderEntry (mediaItem) {
const {toggleMediaSelection, selected, display} = this.props;
return (
);
}
}
================================================
FILE: lib/shared/screens/admin/screens/media/components/media.jsx
================================================
import Animate from 'components/animate';
import Component from 'components/component';
import Content from 'components/content';
import ContentDisplays from 'components/content-displays';
import ContentHeader from 'components/content-header';
import ContentHeaderActions from 'components/content-header-actions';
import ContentSearch from 'components/content-search';
import ModalDelete from 'components/modal-delete';
import Upload from 'components/upload';
import React, {PropTypes} from 'react';
import {mergeFragments} from 'relate-js';
import styles from './media.less';
import List from './list';
import Sorting from './sorting';
import Uploading from './uploading';
export default class Media extends Component {
static fragments = mergeFragments(
List.fragments,
{
mediaCount: 1
}
);
static propTypes = {
media: PropTypes.array.isRequired,
mediaCount: PropTypes.number,
uploadMediaFiles: PropTypes.func.isRequired,
uploadsVisible: PropTypes.bool.isRequired,
search: PropTypes.string.isRequired,
searchChange: PropTypes.func.isRequired,
sort: PropTypes.string.isRequired,
order: PropTypes.string.isRequired,
location: PropTypes.object.isRequired,
toggleMediaSelection: PropTypes.func.isRequired,
selected: PropTypes.array.isRequired,
display: PropTypes.string.isRequired,
changeMediaDisplay: PropTypes.func.isRequired,
unselectAll: PropTypes.func.isRequired,
onRemoveSelected: PropTypes.func.isRequired,
deleteConfirm: PropTypes.bool.isRequired,
cancelRemoveSelected: PropTypes.func.isRequired,
removeSelected: PropTypes.func.isRequired
};
render () {
const {uploadsVisible} = this.props;
return (
{this.renderContent()}
{uploadsVisible && }
);
}
renderContent () {
const {mediaCount} = this.props;
let result;
if (mediaCount === 0) {
result = this.renderNoContent();
} else {
result = this.renderHasContent();
}
return result;
}
renderNoContent () {
const {uploadMediaFiles} = this.props;
return (
You haven’t uploaded any media!
Just drag it into this window or click the upload button bellow.
Just worry about the upload, we’ll take care of categorizing it for you.
Upload
);
}
renderHasContent () {
const {
media,
uploadMediaFiles,
location,
sort,
order,
toggleMediaSelection,
selected,
display,
changeMediaDisplay
} = this.props;
return (
{this.renderSearchOrSelect()}
{this.renderDeleteConfirm()}
);
}
renderSearchOrSelect () {
const {selected, search, searchChange, unselectAll, onRemoveSelected} = this.props;
const len = selected.length;
let result;
if (len) {
result = (
{`You've selected ${len} items`}
);
} else {
result = (
);
}
return result;
}
renderDeleteConfirm () {
const {deleteConfirm, cancelRemoveSelected, removeSelected, selected} = this.props;
if (deleteConfirm) {
return (
);
}
}
}
================================================
FILE: lib/shared/screens/admin/screens/media/components/media.less
================================================
@import '~styles/sizes.less';
@import '~styles/colors.less';
.none {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
}
.noneContent {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -70%);
text-align: center;
width: 100%;
padding: 0 40px;
}
.noneTitle {
font-size: 31px;
color: @adminText;
font-weight: 300;
line-height: 50px;
}
.noneText {
font-size: 15px;
color: @adminText;
font-weight: 300;
}
.uploadButton {
display: inline-block;
margin-top: 30px;
cursor: pointer;
:global {
i, span {
display: inline-block;
vertical-align: top;
color: @primary;
line-height: 25px;
}
span {
font-size: 16px;
text-transform: uppercase;
}
i {
font-size: 22px;
margin-right: 7px;
}
}
}
.selected {
display: inline-block;
vertical-align: top;
}
.selectedText {
font-size: 13px;
color: @adminText;
}
.unselect, .remove {
display: inline-block;
vertical-align: top;
font-size: 13px;
margin-left: 10px;
}
.unselect {
color: @primary;
}
.remove {
color: @alert;
}
================================================
FILE: lib/shared/screens/admin/screens/media/components/menu.jsx
================================================
import Button from 'components/menu-button';
import Component from 'components/component';
import ListHeader from 'components/list-header';
import Scrollable from 'components/scrollable';
import SubButton from 'components/menu-sub-button';
import Upload from 'components/upload';
import React, {PropTypes} from 'react';
import styles from './menu.less';
export default class MediaMenu extends Component {
static propTypes = {
children: PropTypes.node,
pages: PropTypes.array.isRequired,
onBack: PropTypes.func.isRequired,
uploadMediaFiles: PropTypes.func.isRequired,
location: PropTypes.object.isRequired
};
getQuery (type) {
const {location} = this.props;
return Object.assign(
{},
location.query,
{
filter: type
}
);
}
render () {
const {onBack, uploadMediaFiles, location} = this.props;
const {filter = 'all'} = location.query;
return (
);
}
}
================================================
FILE: lib/shared/screens/admin/screens/media/components/menu.less
================================================
@import '~styles/colors.less';
.list {
position: absolute;
top: 70px;
bottom: 0; left: 0; right: 0;
}
.uploadButton {
position: absolute;
top: 0;
right: 0;
padding: 0 10px;
:global {
i {
color: @adminText;
line-height: 70px;
font-size: 22px;
}
}
&:hover :global i {
color: @primary;
}
}
================================================
FILE: lib/shared/screens/admin/screens/media/components/sorting.jsx
================================================
import cx from 'classnames';
import Component from 'components/component';
import React, {PropTypes} from 'react';
import {Link} from 'react-router';
import styles from './sorting.less';
export default class Sorting extends Component {
static propTypes = {
location: PropTypes.object.isRequired,
sort: PropTypes.string.isRequired,
order: PropTypes.string.isRequired
};
reverseOrder (order) {
return order === 'asc' ? 'desc' : 'asc';
}
render () {
return (
Sort by:
{this.renderSort('Date', '_id')}
{this.renderSort('Size', 'filesize')}
{this.renderSort('Dimension', 'dimension')}
);
}
renderSort (label, property) {
const {location, sort, order} = this.props;
const active = sort === property;
const query = Object.assign({}, location.query, {
sort: property,
order: active ? this.reverseOrder(order) : order
});
return (
{label}
{
active &&
}
);
}
}
================================================
FILE: lib/shared/screens/admin/screens/media/components/sorting.less
================================================
@import '~styles/colors.less';
.root {
display: inline-block;
vertical-align: top;
}
.label {
display: inline-block;
vertical-align: top;
color: @adminText;
font-size: 12px;
margin-right: 20px;
line-height: 30px;
}
.button {
display: inline-block;
vertical-align: top;
color: @adminText;
font-size: 14px;
font-weight: 600;
text-decoration: none;
margin-right: 25px;
line-height: 30px;
border: 0;
outline: 0;
:global {
i, span {
display: inline-block;
vertical-align: top;
line-height: 30px;
}
i {
margin-left: 5px;
}
}
&.active {
color: @primary;
}
}
================================================
FILE: lib/shared/screens/admin/screens/media/components/uploading/index.js
================================================
import bind from 'decorators/bind';
import Component from 'components/component';
import React, {PropTypes} from 'react';
import {connect} from 'react-redux';
import Uploading from './uploading';
@connect(
(state) => ({
uploads: state.media.uploads
})
)
export default class UploadingContainer extends Component {
static propTypes = {
uploads: PropTypes.array.isRequired
};
getInitState () {
return {
opened: true
};
}
@bind
toggleOpened () {
this.setState({
opened: !this.state.opened
});
}
render () {
const {uploads} = this.props;
const {opened} = this.state;
return (
);
}
}
================================================
FILE: lib/shared/screens/admin/screens/media/components/uploading/item.jsx
================================================
import cx from 'classnames';
import Animate from 'components/animate';
import Component from 'components/component';
import Spinner from 'components/spinner';
import React, {PropTypes} from 'react';
import styles from './item.less';
export default class UploadItem extends Component {
static propTypes = {
name: PropTypes.string.isRequired,
status: PropTypes.string.isRequired
};
render () {
const {name} = this.props;
return (
{name}
{this.renderStatus()}
);
}
renderStatus () {
const {status} = this.props;
let result;
if (status === 'queue') {
result = ;
} else if (status === 'uploading') {
result = ;
} else if (status === 'success') {
result = ;
} else if (status === 'error') {
result = ;
}
return (
{result}
);
}
}
================================================
FILE: lib/shared/screens/admin/screens/media/components/uploading/item.less
================================================
@import '~styles/colors.less';
@height: 40px;
.root {
height: @height;
display: table;
width: 100%;
table-layout: fixed;
}
.name, .status {
display: table-cell;
vertical-align: top;
line-height: @height;
}
.name {
color: @adminText;
font-size: 12px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.status {
text-align: center;
width: 35px;
:global i {
font-size: 10px;
line-height: @height;
}
}
.uploading {
padding-top: 8px;
}
.success {
:global i {
color: @success;
}
}
.queue {
:global i {
color: @adminText;
}
}
.error {
:global i {
color: @alert;
}
}
================================================
FILE: lib/shared/screens/admin/screens/media/components/uploading/uploading.jsx
================================================
import velocity from 'velocity-animate';
import Animate from 'components/animate';
import Component from 'components/component';
import Scrollable from 'components/scrollable';
import React, {PropTypes} from 'react';
import styles from './uploading.less';
import Item from './item';
export default class Uploading extends Component {
static propTypes = {
uploads: PropTypes.array.isRequired,
opened: PropTypes.bool.isRequired,
toggleOpened: PropTypes.func.isRequired
};
componentWillReceiveProps (nextProps) {
if (this.props.opened !== nextProps.opened) {
const config = {
duration: 500,
easing: 'easeOutExpo'
};
if (nextProps.opened) {
velocity(this.refs.root, {translateY: '0%'}, config);
} else {
velocity(this.refs.root, {translateY: '100%'}, config);
}
}
}
render () {
const {uploads, toggleOpened} = this.props;
const uploadsFinished = uploads.reduce((previousValue, current) => {
const isFinished = current.status !== 'queue' && current.status !== 'uploading';
return previousValue + (isFinished ? 1 : 0);
}, 0);
return (
Uploads
{uploadsFinished}
/
{uploads.length}
{uploads.map(this.renderUpload, this)}
);
}
renderUpload (item) {
return (
);
}
}
================================================
FILE: lib/shared/screens/admin/screens/media/components/uploading/uploading.less
================================================
@import '~styles/colors.less';
@headerHeight: 45px;
@contentHeight: 280px;
.root {
position: absolute;
right: 7%;
bottom: 0;
width: 370px;
border: 1px solid @adminBorders;
background-color: #fff;
}
.header {
height: @headerHeight;
padding: 0 20px;
position: absolute;
top: -@headerHeight;
left: -1px; right: -1px;
border: 1px solid @adminBorders;
cursor: pointer;
background-color: #f8f8f8;
border-radius: 3px 3px 0 0;
}
.title {
font-size: 14px;
font-weight: 400;
color: @adminText;
line-height: @headerHeight;
}
.progress {
float: right;
font-size: 13px;
font-weight: 400;
color: @primary;
line-height: @headerHeight;
}
.list {
height: @contentHeight;
}
.listContent {
padding: 20px 30px;
}
================================================
FILE: lib/shared/screens/admin/screens/media/index.js
================================================
import * as mediaActions from 'actions/media';
import bind from 'decorators/bind';
import debounce from 'decorators/debounce';
import Component from 'components/component';
import React, {PropTypes} from 'react';
import {getMimeTypes} from 'helpers/mime-types';
import {bindActionCreators} from 'redux';
import {pushState} from 'redux-router';
import {dataConnect} from 'relate-js';
import Media from './components/media.jsx';
@dataConnect(
(state) => ({
uploadsVisible: state.media.uploads.length > 0,
display: state.media.display,
location: state.router.location,
sort: state.router.location.query.sort || '_id',
order: state.router.location.query.order || 'desc',
search: state.router.location.query.s || '',
filter: state.router.location.query.filter || 'all'
}),
(dispatch) => bindActionCreators(mediaActions, dispatch),
(props) => {
const filter = props.filter;
let filters = [];
if (filter !== 'all') {
let type = getMimeTypes(filter);
let op = 'in';
if (!type) {
op = 'eq';
type = filter;
}
filters = [{
property: 'type',
op: {
[op]: type
}
}];
}
return {
fragments: Media.fragments,
variablesTypes: {
media: {
sort: 'String',
order: 'String',
search: 'String',
s: 'String',
filters: '[Filter]'
}
},
initialVariables: {
media: {
sort: props.sort,
order: props.order,
search: 'name',
s: props.search,
filters
}
},
mutations: {
addMedia: [
{
type: 'PREPEND',
field: 'media'
},
{
type: 'INCREMENT',
field: 'mediaCount'
}
]
}
};
}
)
export default class MediaContainer extends Component {
static propTypes = {
media: PropTypes.array.isRequired,
sort: PropTypes.string.isRequired,
order: PropTypes.string.isRequired,
search: PropTypes.string.isRequired,
filter: PropTypes.string.isRequired,
location: PropTypes.object.isRequired,
changeMediaDisplay: PropTypes.func.isRequired
};
static contextTypes = {
store: PropTypes.object.isRequired
};
static defaultProps = {
media: []
};
getInitState () {
return {
search: this.props.search,
selected: [],
deleteConfirm: false
};
}
componentWillReceiveProps (nextProps) {
if (nextProps.sort !== this.props.sort ||
nextProps.order !== this.props.order ||
nextProps.search !== this.props.search ||
nextProps.filter !== this.props.filter) {
this.updateMedia(nextProps);
}
}
updateMedia (props) {
const {relate, sort, order, search, filter} = props;
const mediaVariables = {
sort,
order,
search: 'name',
s: search
};
if (filter !== 'all') {
let type = getMimeTypes(filter);
let op = 'in';
if (!type) {
op = 'eq';
type = filter;
}
mediaVariables.filters = [{
property: 'type',
op: {
[op]: type
}
}];
}
relate.setVariables({
media: mediaVariables
});
}
@bind
toggleMediaSelection (id) {
const {selected} = this.state;
const index = selected.indexOf(id);
if (index === -1) {
this.setState({
selected: [...selected, id]
});
} else {
const newSelected = selected.slice(0);
newSelected.splice(index, 1);
this.setState({
selected: newSelected
});
}
}
@bind
unselectAll () {
this.setState({
selected: []
});
}
@bind
searchChange (search) {
this.setState({
search
});
this.updateSearch();
}
@debounce(300)
updateSearch () {
const {location} = this.props;
const query = Object.assign({}, location.query, {
s: this.state.search
});
this.context.store.dispatch(pushState(null, location, query));
}
@bind
onRemoveSelected () {
this.setState({
deleteConfirm: true
});
}
@bind
cancelRemoveSelected () {
this.setState({
deleteConfirm: false
});
}
@bind
removeSelected () {
this.props
.removeMediaItems(this.state.selected)
.then(() => {
this.setState({
selected: [],
deleteConfirm: false
});
});
}
render () {
const {
media,
mediaCount,
uploadMediaFiles,
uploadsVisible,
location,
sort,
order,
display,
changeMediaDisplay
} = this.props;
return (
);
}
}
================================================
FILE: lib/shared/screens/admin/screens/media/menu.js
================================================
import * as adminMenuActions from 'actions/admin-menu';
import * as mediaActions from 'actions/media';
import bind from 'decorators/bind';
import Component from 'components/component';
import React, {PropTypes} from 'react';
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
import Menu from './components/menu';
@connect(
(state) => ({
location: state.router.location
}),
(dispatch) => ({
...bindActionCreators(adminMenuActions, dispatch),
...bindActionCreators(mediaActions, dispatch)
})
)
export default class MediaMenuContainer extends Component {
static propTypes = {
closeAdminMenu: PropTypes.func.isRequired,
openAdminMenu: PropTypes.func.isRequired,
uploadMediaFiles: PropTypes.func.isRequired,
location: PropTypes.object.isRequired
};
componentDidMount () {
this.props.openAdminMenu();
}
@bind
onBack () {
this.props.closeAdminMenu();
}
render () {
const {uploadMediaFiles, location} = this.props;
return (
);
}
}
================================================
FILE: lib/shared/screens/admin/screens/menus/components/content.jsx
================================================
import Component from 'components/component';
import React, {PropTypes} from 'react';
import styles from './content.less';
import New from './new';
export default class Content extends Component {
static propTypes = {
children: PropTypes.node,
count: PropTypes.number
};
render () {
let result;
if (this.props.count === 0) {
result = this.renderNoMenus();
} else if (this.props.children) {
result = this.props.children;
} else {
result = this.renderEmpty();
}
return result;
}
renderEmpty () {
return (
Relax, select a menu first!
);
}
renderNoMenus () {
return (
);
}
}
================================================
FILE: lib/shared/screens/admin/screens/menus/components/content.less
================================================
@import '~styles/sizes.less';
@import '~styles/colors.less';
.empty {
position: absolute;
top: 40%;
left: 50%;
transform: translate(-50%, -50%);
text-align: center;
:global i {
font-size: 56px;
color: @adminTextSub;
margin-bottom: 20px;
margin-top: 20px;
}
}
.emptyText {
font-size: 17px;
color: @adminTextSub;
}
.noMenus {
width: 440px;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
text-align: center;
}
.noTitle {
font-size: 36px;
font-weight: 300;
margin-bottom: 5px;
color: @adminText;
}
================================================
FILE: lib/shared/screens/admin/screens/menus/components/entry.jsx
================================================
import cx from 'classnames';
import moment from 'moment';
import Component from 'components/component';
import React, {PropTypes} from 'react';
import {Link} from 'react-router';
import styles from './entry.less';
export default class MenuEntry extends Component {
static fragments = {
menu: {
_id: 1,
title: 1,
date: 1
}
};
static propTypes = {
menu: PropTypes.object.isRequired,
active: PropTypes.bool.isRequired,
query: PropTypes.object.isRequired
};
render () {
const {menu, active, query} = this.props;
const date = moment(menu.date).fromNow();
const editLink = '/admin/menus/' + menu._id;
return (
{menu.title}
{date}
);
}
}
================================================
FILE: lib/shared/screens/admin/screens/menus/components/entry.less
================================================
@import '~styles/colors.less';
.root {
height: 70px;
border-bottom: 1px solid @adminBorders;
padding: 16px 15px;
position: relative;
display: block;
text-decoration: none;
&:hover {
background-color: @primaryBack;
}
}
.title {
font-size: 15px;
color: @adminTextHighlight;
}
.date {
font-size: 12px;
color: @adminText;
font-weight: 300;
}
.active {
background-color: @primary;
.title {
color: #ffffff;
}
.date {
color: #ffffff;
}
&:hover {
background-color: @primary;
}
}
================================================
FILE: lib/shared/screens/admin/screens/menus/components/list.jsx
================================================
import Component from 'components/component';
import React, {PropTypes} from 'react';
import Entry from './entry';
export default class MenusList extends Component {
static fragments = {
menus: Entry.fragments.menu
};
static propTypes = {
menus: PropTypes.array.isRequired,
activeId: PropTypes.string,
query: PropTypes.object.isRequired
};
render () {
return (
{this.props.menus.map(this.renderEntry, this)}
);
}
renderEntry (menu, key) {
const {activeId, query} = this.props;
return (
);
}
}
================================================
FILE: lib/shared/screens/admin/screens/menus/components/menu.jsx
================================================
import Component from 'components/component';
import ListHeader from 'components/list-header';
import ListSearchSort from 'components/list-search-sort';
import Modal from 'components/modal';
import Scrollable from 'components/scrollable';
import React, {PropTypes} from 'react';
import styles from './menu.less';
import List from './list';
import New from './new';
const sorts = [
{
label: 'Date desc',
sort: '_id',
order: 'desc'
},
{
label: 'Date asc',
sort: '_id',
order: 'asc'
},
{
label: 'Title A-Z',
sort: 'title',
order: 'asc'
},
{
label: 'Title Z-A',
sort: 'title',
order: 'desc'
},
{
label: 'Updated desc',
sort: 'updatedDate',
order: 'desc'
},
{
label: 'Updated asc',
sort: 'updatedDate',
order: 'asc'
}
];
export default class Menu extends Component {
static fragments = List.fragments;
static propTypes = {
children: PropTypes.node,
menus: PropTypes.array.isRequired,
onBack: PropTypes.func.isRequired,
newOpened: PropTypes.bool.isRequired,
onNew: PropTypes.func.isRequired,
closeNew: PropTypes.func.isRequired,
activeId: PropTypes.string,
sort: PropTypes.string.isRequired,
order: PropTypes.string.isRequired,
search: PropTypes.string.isRequired,
location: PropTypes.object.isRequired
};
render () {
const {menus, onBack, onNew, activeId, search, sort, order, location} = this.props;
return (
{this.renderNew()}
);
}
renderNew () {
const {newOpened, closeNew} = this.props;
if (newOpened) {
return (
);
}
}
}
================================================
FILE: lib/shared/screens/admin/screens/menus/components/menu.less
================================================
.list {
position: absolute;
top: 110px;
bottom: 0; left: 0; right: 0;
}
================================================
FILE: lib/shared/screens/admin/screens/menus/components/new/index.js
================================================
import Component from 'components/component';
import React, {PropTypes} from 'react';
import {addMenu} from 'actions/menu';
import New from './new';
export default class NewMenuContainer extends Component {
static propTypes = {
onClose: PropTypes.func.isRequired
};
static contextTypes = {
store: PropTypes.object.isRequired
};
getInitState () {
return {
title: '',
loading: false
};
}
changeTitle (title) {
this.setState({
title
});
}
submit () {
if (!this.state.loading) {
this.setState({
loading: true
}, () => {
const {store} = this.context;
const {onClose} = this.props;
const {title} = this.state;
store.dispatch(addMenu({title}, true)).then(() => {
onClose && onClose();
});
});
}
}
render () {
return (
);
}
}
================================================
FILE: lib/shared/screens/admin/screens/menus/components/new/new.jsx
================================================
import Component from 'components/component';
import ModalInput from 'components/modal-input';
import ModalNew from 'components/modal-new';
import React, {PropTypes} from 'react';
export default class NewMenu extends Component {
static propTypes = {
title: PropTypes.string.isRequired,
changeTitle: PropTypes.func.isRequired,
submit: PropTypes.func.isRequired,
loading: PropTypes.bool
};
render () {
const {title, changeTitle, submit, loading} = this.props;
return (
);
}
}
================================================
FILE: lib/shared/screens/admin/screens/menus/index.js
================================================
import Component from 'components/component';
import React, {PropTypes} from 'react';
import {dataConnect} from 'relate-js';
import Content from './components/content.jsx';
@dataConnect(
() => ({
fragments: {
menusCount: true
},
mutations: {
addMenu: [{
type: 'INCREMENT',
field: 'menusCount'
}],
removeMenu: [{
type: 'DECREMENT',
field: 'menusCount'
}]
}
})
)
export default class MenusContainer extends Component {
static propTypes = {
children: PropTypes.node,
menusCount: PropTypes.number
};
render () {
const {menusCount} = this.props;
return (
{this.props.children}
);
}
}
================================================
FILE: lib/shared/screens/admin/screens/menus/menu.js
================================================
import * as adminMenuActions from 'actions/admin-menu';
import Component from 'components/component';
import React, {PropTypes} from 'react';
import {bindActionCreators} from 'redux';
import {dataConnect} from 'relate-js';
import Menu from './components/menu';
@dataConnect(
(state) => ({
activeId: state.router.params.id,
location: state.router.location,
sort: state.router.location.query.sort || '_id',
order: state.router.location.query.order || 'desc',
search: state.router.location.query.s || ''
}),
(dispatch) => bindActionCreators(adminMenuActions, dispatch),
(props) => ({
fragments: Menu.fragments,
variablesTypes: {
menus: {
sort: 'String',
order: 'String',
search: 'String',
s: 'String'
}
},
initialVariables: {
menus: {
sort: props.sort,
order: props.order,
search: 'title',
s: props.search
}
},
mutations: {
addMenu: [{
type: 'PREPEND',
field: 'menus'
}]
}
})
)
export default class MenusMenuContainer extends Component {
static propTypes = {
menus: PropTypes.array.isRequired,
closeAdminMenu: PropTypes.func.isRequired,
openAdminMenu: PropTypes.func.isRequired,
activeId: PropTypes.string,
sort: PropTypes.string.isRequired,
order: PropTypes.string.isRequired,
search: PropTypes.string.isRequired
};
static defaultProps = {
menus: []
};
getInitState () {
return {
newOpened: false
};
}
componentDidMount () {
this.props.openAdminMenu();
}
componentWillReceiveProps (nextProps) {
if (nextProps.sort !== this.props.sort || nextProps.order !== this.props.order || nextProps.search !== this.props.search) {
this.props.relate.setVariables({
menus: {
sort: nextProps.sort,
order: nextProps.order,
search: 'title',
s: nextProps.search
}
});
}
}
onBack () {
this.props.closeAdminMenu();
}
onNew () {
this.setState({
newOpened: true
});
}
closeNew () {
this.setState({
newOpened: false
});
}
render () {
const {menus, activeId, order, sort, search, location} = this.props;
return (
);
}
}
================================================
FILE: lib/shared/screens/admin/screens/menus/screens/menu/components/menu-builder/builder.jsx
================================================
import cx from 'classnames';
import Component from 'components/component';
import React from 'react';
import Scrollable from 'components/scrollable';
import styles from './builder.less';
import Collapsable from './collapsable';
import LinkBuilder from './link';
import Menu from './menu';
import Pages from './pages';
export default class MenuBuilder extends Component {
render () {
return (
);
}
}
================================================
FILE: lib/shared/screens/admin/screens/menus/screens/menu/components/menu-builder/builder.less
================================================
@import '~styles/colors.less';
.root {
max-width: 672px;
margin: 0 auto;
}
.part {
display: inline-block;
vertical-align: top;
position: absolute;
top: 0;
bottom: 0;
}
.left, .right {
width: 50%;
}
.left {
left: 0;
margin-right: 50px;
}
.leftContent {
position: absolute;
top: 0;
right: 70px;
left: 0;
bottom: 0;
.innerScroll {
width: 270px;
float: right;
}
}
.innerScroll {
padding-right: 20px;
}
.right {
right: 0;
padding-left: 70px;
}
.rightContent {
position: absolute;
top: 0;
left: 70px;
right: 0;
bottom: 0;
}
.center {
left: 50%;
width: 2px;
background-color: @adminBorders;
}
.icon {
position: absolute;
top: 200px;
left: -20px;
font-size: 46px;
color: @adminBorders;
&:before {
z-index: 1;
position: relative;
}
&:after {
content: '';
position: absolute;
left: 0; right: 0; top: 50%;
height: 7px;
background-color: #ffffff;
}
}
================================================
FILE: lib/shared/screens/admin/screens/menus/screens/menu/components/menu-builder/collapsable.jsx
================================================
import bind from 'decorators/bind';
import cx from 'classnames';
import Component from 'components/component';
import React, {PropTypes} from 'react';
import styles from './collapsable.less';
export default class Collapsable extends Component {
static propTypes = {
title: PropTypes.string.isRequired,
icon: PropTypes.string.isRequired,
children: PropTypes.node.isRequired,
defaultOpened: PropTypes.bool.isRequired
};
static defaultProps = {
defaultOpened: true
};
getInitState () {
return {
opened: this.props.defaultOpened
};
}
@bind
onClick () {
this.setState({
opened: !this.state.opened
});
}
render () {
const {opened} = this.state;
const {title, icon} = this.props;
return (
{title}
{opened && this.props.children}
);
}
}
================================================
FILE: lib/shared/screens/admin/screens/menus/screens/menu/components/menu-builder/collapsable.less
================================================
@import '~styles/colors.less';
.root {
margin-bottom: 35px;
}
.header {
cursor: pointer;
margin-bottom: 10px;
}
.icon, .title, .headerIcon {
display: inline-block;
vertical-align: top;
line-height: 30px;
color: @adminText;
}
.icon {
font-size: 24px;
margin-right: 13px;
}
.title {
font-size: 14px;
}
.headerIcon {
float: right;
font-size: 12px;
}
================================================
FILE: lib/shared/screens/admin/screens/menus/screens/menu/components/menu-builder/entry.jsx
================================================
import bind from 'decorators/bind';
import cx from 'classnames';
import Animate from 'components/animate';
import Component from 'components/component';
import Draggable from 'components/dnd/draggable';
import Droppable from 'components/dnd/droppable';
import React, {PropTypes} from 'react';
import styles from './entry.less';
export default class MenuEntry extends Component {
static propTypes = {
item: PropTypes.object.isRequired,
children: PropTypes.node,
dragging: PropTypes.bool,
draggedMenuItem: PropTypes.func,
positionInParent: PropTypes.number,
draggingSelf: PropTypes.bool
};
static defaultProps = {
item: {}
};
render () {
const {item, positionInParent, draggingSelf} = this.props;
const {label, type} = item;
const isNew = !(item.id && true);
const dragInfo = {
type: isNew ? 'new' : 'move',
item,
id: item.id,
parentId: item.parent,
positionInParent
};
return (
{label || type === 'url' && isNew && 'Where Link appears to be dragged'}
{!isNew && this.renderChildren()}
);
}
renderChildren () {
const {item, dragging} = this.props;
return (
{this.props.children}
);
}
@bind
renderPlaceholder ({isActive}) {
let placeholderContent;
if (isActive) {
placeholderContent = (
);
} else {
placeholderContent = (
Drop here for sub link!
);
}
return (
{placeholderContent}
);
}
}
================================================
FILE: lib/shared/screens/admin/screens/menus/screens/menu/components/menu-builder/entry.less
================================================
@import '~styles/colors.less';
.root {
}
.draggingSelf {
opacity: 0.5;
}
.info {
font-size: 14px;
padding: 3px 7px;
line-height: 24px;
border-radius: 4px;
margin: 7px 0;
cursor: move;
}
.page {
background-color: @primaryBack;
color: @primary;
}
.url {
background-color: #FFFBBD;
color: #C09F00;
}
.sub {
margin-left: 30px;
margin-bottom: 7px;
}
.empty {
display: block;
border: 1px solid @adminBorders;
border-radius: 2px;
line-height: 30px;
height: 30px;
padding: 0px 7px;
}
.emptyText {
color: @adminText;
font-size: 12px;
}
.dropIcon {
font-size: 18px;
line-height: 28px;
color: @dropSuccess;
}
.active {
background-color: @dropSuccessBack;
border-color: @dropSuccess;
text-align: center;
}
================================================
FILE: lib/shared/screens/admin/screens/menus/screens/menu/components/menu-builder/index.js
================================================
import Component from 'components/component';
import React from 'react';
import MenuBuilder from './builder';
export default class MenuBuilderContainer extends Component {
render () {
return (
);
}
}
================================================
FILE: lib/shared/screens/admin/screens/menus/screens/menu/components/menu-builder/link/index.js
================================================
import bind from 'decorators/bind';
import Component from 'components/component';
import React from 'react';
import LinkBuilder from './link';
export default class LinkContainer extends Component {
getInitState () {
return {
label: '',
url: ''
};
}
@bind
changeLabel (label) {
this.setState({
label
});
}
@bind
changeUrl (url) {
this.setState({
url
});
}
render () {
return (
);
}
}
================================================
FILE: lib/shared/screens/admin/screens/menus/screens/menu/components/menu-builder/link/link.jsx
================================================
import Component from 'components/component';
import Input from 'components/input-options/input';
import React, {PropTypes} from 'react';
import styles from './link.less';
import Entry from '../entry';
export default class Link extends Component {
static propTypes = {
label: PropTypes.string.isRequired,
url: PropTypes.string.isRequired,
changeLabel: PropTypes.func.isRequired,
changeUrl: PropTypes.func.isRequired
};
render () {
const {changeLabel, changeUrl, label, url} = this.props;
const item = {
type: 'url',
url,
label
};
return (
);
}
}
================================================
FILE: lib/shared/screens/admin/screens/menus/screens/menu/components/menu-builder/link/link.less
================================================
@import '~styles/colors.less';
.option {
display: block;
margin-bottom: 15px;
}
.label {
color: @adminText;
font-size: 14px;
line-height: 32px;
}
================================================
FILE: lib/shared/screens/admin/screens/menus/screens/menu/components/menu-builder/menu/index.js
================================================
import Component from 'components/component';
import React, {PropTypes} from 'react';
import {draggedMenuItem} from 'actions/menu';
import {bindActionCreators} from 'redux';
import {dataConnect} from 'relate-js';
import Menu from './menu';
@dataConnect(
(state) => ({
menuId: state.router.params.id,
dragging: state.dnd.dragging,
menuData: state.menu,
draggingId: state.dnd.dragInfo.id
}),
(dispatch) => bindActionCreators({draggedMenuItem}, dispatch),
(props) => ({
fragments: Menu.fragments,
variablesTypes: {
menu: {
_id: 'ID!'
}
},
initialVariables: {
menu: {
_id: props.menuId
}
}
})
)
export default class MenuContainer extends Component {
static propTypes = {
menuId: PropTypes.string.isRequired
};
componentWillReceiveProps (nextProps) {
if (this.props.menuId !== nextProps.menuId) {
this.props.relate.setVariables({
menu: {
_id: nextProps.menuId
}
});
}
}
render () {
return (
);
}
}
================================================
FILE: lib/shared/screens/admin/screens/menus/screens/menu/components/menu-builder/menu/menu.jsx
================================================
import bind from 'decorators/bind';
import cx from 'classnames';
import Animate from 'components/animate';
import Component from 'components/component';
import Dragger from 'components/dnd/dragger';
import Droppable from 'components/dnd/droppable';
import React, {PropTypes} from 'react';
import styles from './menu.less';
import Entry from '../entry';
export default class Menu extends Component {
static fragments = {
menu: {
_id: 1,
data: 1
}
};
static propTypes = {
dragging: PropTypes.bool.isRequired,
draggingId: PropTypes.number,
draggedMenuItem: PropTypes.func.isRequired,
menuData: PropTypes.object.isRequired
};
render () {
const {menuData} = this.props;
const hasChildren = menuData && menuData.root && menuData.root.children.length > 0;
return (
{
hasChildren &&
this.renderChildren(menuData.root.children)
}
{this.renderDragger()}
);
}
renderChildren (children) {
return children.map(this.renderEntry, this);
}
renderEntry (id, index) {
const {menuData, dragging, draggingId, draggedMenuItem} = this.props;
const item = menuData[id];
return (
{item.children && this.renderChildren(item.children)}
);
}
@bind
renderPlaceholder ({isActive}) {
let placeholderContent;
if (isActive) {
placeholderContent = (
);
} else {
placeholderContent = (
Drop your links here!
Drag pages or custom links from the left and drop them in this
section to start building your menu structure.
Remember you can also sort them by moving them above or bellow other items.
);
}
return (
);
}
renderDragger () {
const {dragging, draggedMenuItem} = this.props;
if (dragging) {
return (
);
}
}
}
================================================
FILE: lib/shared/screens/admin/screens/menus/screens/menu/components/menu-builder/menu/menu.less
================================================
@import '~styles/colors.less';
.empty {
text-align: center;
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
transition: all 0.2s ease-out;
background-color: transparent;
border: 1px solid transparent;
.emptyContent {
position: absolute;
top: 50%;
left: 0;
right: 0;
transform: translateY(-60%);
}
.title {
color: @adminText;
font-size: 36px;
font-weight: 300;
}
.text {
color: @adminText;
font-size: 16px;
margin-bottom: 20px;
}
}
.items {
width: 250px;
padding-top: 32px;
padding-bottom: 32px;
}
.dropIcon {
font-size: 40px;
color: @dropSuccess;
}
.active {
background-color: @dropSuccessBack;
border: 1px solid @dropSuccess;
border-radius: 2px;
}
================================================
FILE: lib/shared/screens/admin/screens/menus/screens/menu/components/menu-builder/pages/index.js
================================================
import Component from 'components/component';
import React, {PropTypes} from 'react';
import {dataConnect} from 'relate-js';
import Pages from './pages';
@dataConnect(
() => ({
fragments: Pages.fragments
})
)
export default class MenuBuilderContainer extends Component {
static propTypes = {
pages: PropTypes.array
};
static defaultProps = {
pages: []
};
render () {
return (
);
}
}
================================================
FILE: lib/shared/screens/admin/screens/menus/screens/menu/components/menu-builder/pages/pages.jsx
================================================
import Component from 'components/component';
import Spinner from 'components/spinner';
import React, {PropTypes} from 'react';
import styles from './pages.less';
import Entry from '../entry';
export default class PagesList extends Component {
static fragments = {
pages: {
_id: 1,
title: 1
}
};
static propTypes = {
pages: PropTypes.array.isRequired,
loading: PropTypes.bool.isRequired
};
render () {
return (
{this.renderPages()}
);
}
renderPages () {
const {pages} = this.props;
let result;
if (this.props.loading) {
result = (
);
} else if (this.props.pages.length > 0) {
result = pages.map(this.renderPage, this);
} else {
result = (
No pages to show
);
}
return result;
}
renderPage (page) {
const item = {
type: 'page',
typeProps: {
pageId: page._id
},
label: page.title
};
return (
);
}
}
================================================
FILE: lib/shared/screens/admin/screens/menus/screens/menu/components/menu-builder/pages/pages.less
================================================
@import '~styles/colors.less';
.no {
font-size: 13px;
color: @adminText;
padding: 15px 0;
}
.loading {
text-align: center;
}
================================================
FILE: lib/shared/screens/admin/screens/menus/screens/menu/components/menu.jsx
================================================
import cx from 'classnames';
import Component from 'components/component';
import ContentHeader from 'components/content-header';
import ContentHeaderActions from 'components/content-header-actions';
import EditableTitle from 'components/editable-title';
import ModalDelete from 'components/modal-delete';
import React, {PropTypes} from 'react';
import {mergeFragments} from 'relax-fragments';
import styles from './menu.less';
import MenuBuilder from './menu-builder';
import State from './state';
export default class Menu extends Component {
static fragments = mergeFragments({
menu: {
_id: 1,
title: 1
}
});
static propTypes = {
menu: PropTypes.object.isRequired,
onDelete: PropTypes.func.isRequired,
cancelDelete: PropTypes.func.isRequired,
deleteConfirm: PropTypes.bool.isRequired,
confirmDelete: PropTypes.func.isRequired,
updateTitle: PropTypes.func.isRequired,
saveMenu: PropTypes.func.isRequired,
state: PropTypes.string
};
render () {
const {menu, onDelete, updateTitle} = this.props;
return (
{this.renderState()}
{this.renderDeleteConfirm()}
);
}
renderState () {
const {saveMenu, state} = this.props;
return ;
}
renderDeleteConfirm () {
const {deleteConfirm, cancelDelete, confirmDelete, menu} = this.props;
if (deleteConfirm) {
return (
);
}
}
}
================================================
FILE: lib/shared/screens/admin/screens/menus/screens/menu/components/menu.less
================================================
@import '~styles/colors.less';
.info {
display: inline-block;
vertical-align: top;
}
.actionButton {
color: @alert;
font-weight: 300;
font-size: 13px;
text-transform: uppercase;
margin-left: 20px;
}
.save {
color: @primary;
}
.state {
display: inline-block;
vertical-align: top;
text-align: center;
}
.content {
position: absolute;
top: 70px;
left: 0;
right: 0;
bottom: 0;
margin: 50px;
margin-bottom: 30px;
}
================================================
FILE: lib/shared/screens/admin/screens/menus/screens/menu/components/state.jsx
================================================
import cx from 'classnames';
import Animate from 'components/animate';
import Component from 'components/component';
import Spinner from 'components/spinner';
import React, {PropTypes} from 'react';
import styles from './state.less';
export default class MenuState extends Component {
static propTypes = {
state: PropTypes.string,
saveMenu: PropTypes.func.isRequired
};
render () {
const {state, saveMenu} = this.props;
let result;
if (state === 'saving') {
result = (
);
} else if (state === 'success') {
result = (
);
} else {
result = (
);
}
return result;
}
}
================================================
FILE: lib/shared/screens/admin/screens/menus/screens/menu/components/state.less
================================================
@import '~styles/colors.less';
.actionButton {
color: @alert;
font-weight: 300;
font-size: 13px;
text-transform: uppercase;
margin-left: 20px;
line-height: 30px;
}
.save {
color: @primary;
}
.icon {
display: inline-block;
vertical-align: top;
padding: 0 24px;
color: @success;
font-size: 22px;
line-height: 30px;
}
================================================
FILE: lib/shared/screens/admin/screens/menus/screens/menu/index.js
================================================
import bind from 'decorators/bind';
import debounce from 'decorators/debounce';
import Component from 'components/component';
import React, {PropTypes} from 'react';
import {removeMenu, updateMenuTitle, updateMenuData} from 'actions/menu';
import {bindActionCreators} from 'redux';
import {dataConnect} from 'relate-js';
import Menu from './components/menu';
@dataConnect(
(state) => ({
params: state.router.params
}),
(dispatch) => bindActionCreators({removeMenu, updateMenuTitle, updateMenuData}, dispatch),
(props) => ({
fragments: Menu.fragments,
variablesTypes: {
menu: {
_id: 'ID!'
}
},
initialVariables: {
menu: {
_id: props.params.id
}
}
})
)
export default class MenuContainer extends Component {
static propTypes = {
params: PropTypes.object.isRequired
};
static defaultProps = {
menu: {}
};
getInitState () {
return {
deleteConfirm: false
};
}
componentWillReceiveProps (nextProps) {
if (this.props.params.id !== nextProps.params.id) {
this.props.relate.setVariables({
menu: {
_id: nextProps.params.id
}
});
}
}
@bind
onDelete () {
this.setState({
deleteConfirm: true
});
}
@bind
cancelDelete () {
this.setState({
deleteConfirm: false
});
}
@bind
confirmDelete () {
const {params} = this.props;
this.props.removeMenu(params.id, true);
}
@bind
updateTitle (title) {
const {menu} = this.props;
return this.props.updateMenuTitle(menu._id, title);
}
@debounce(2000)
successOut () {
this.setState({
state: null
});
}
@bind
saveMenu () {
const {menu} = this.props;
this.setState({
state: 'saving'
}, () => {
this.props
.updateMenuData(menu._id)
.then(() => {
this.setState({
state: 'success'
});
this.successOut();
});
});
}
render () {
const {menu} = this.props;
return (
);
}
}
================================================
FILE: lib/shared/screens/admin/screens/pages/components/entry.jsx
================================================
import cx from 'classnames';
import moment from 'moment';
import Component from 'components/component';
import React, {PropTypes} from 'react';
import {Link} from 'react-router';
import styles from './entry.less';
export default class PagesEntry extends Component {
static fragments = {
page: {
_id: 1,
title: 1,
state: 1,
date: 1
}
};
static propTypes = {
page: PropTypes.object.isRequired,
active: PropTypes.bool.isRequired,
query: PropTypes.object.isRequired
};
render () {
const {page, active, query} = this.props;
const date = moment(page.date).fromNow();
const editLink = `/admin/pages/${page._id}`;
return (
);
}
}
================================================
FILE: lib/shared/screens/admin/screens/pages/components/entry.less
================================================
@import '~styles/colors.less';
.root {
height: 70px;
border-bottom: 1px solid @adminBorders;
padding: 13px;
position: relative;
display: block;
text-decoration: none;
&:hover {
background-color: @primaryBack;
}
}
.status {
position: absolute;
left: 13px;
top: 30px;
width: 10px;
height: 10px;
background-color: @draft;
border-radius: 50%;
}
.published {
background-color: @primary;
}
.info {
padding-left: 25px;
padding-top: 3px;
}
.title {
font-size: 15px;
color: @adminTextHighlight;
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
}
.date {
font-size: 12px;
color: @adminText;
font-weight: 300;
}
.active {
background-color: @primary;
.status.published {
background-color: #ffffff;
}
.title {
color: #ffffff;
}
.date {
color: #ffffff;
}
&:hover {
background-color: @primary;
}
}
================================================
FILE: lib/shared/screens/admin/screens/pages/components/list.jsx
================================================
import Component from 'components/component';
import React, {PropTypes} from 'react';
import Entry from './entry';
export default class PagesList extends Component {
static fragments = {
pages: Entry.fragments.page
};
static propTypes = {
pages: PropTypes.array.isRequired,
activePageId: PropTypes.string,
query: PropTypes.object.isRequired
};
render () {
return (
{this.props.pages.map(this.renderEntry, this)}
);
}
renderEntry (page, key) {
const {activePageId, query} = this.props;
return (
);
}
}
================================================
FILE: lib/shared/screens/admin/screens/pages/components/menu.jsx
================================================
import Component from 'components/component';
import ListHeader from 'components/list-header';
import ListSearchSort from 'components/list-search-sort';
import Modal from 'components/modal';
import Scrollable from 'components/scrollable';
import React, {PropTypes} from 'react';
import styles from './menu.less';
import List from './list';
import New from './new';
const sorts = [
{
label: 'Date desc',
sort: '_id',
order: 'desc'
},
{
label: 'Date asc',
sort: '_id',
order: 'asc'
},
{
label: 'Title A-Z',
sort: 'title',
order: 'asc'
},
{
label: 'Title Z-A',
sort: 'title',
order: 'desc'
},
{
label: 'Updated desc',
sort: 'updatedDate',
order: 'desc'
},
{
label: 'Updated asc',
sort: 'updatedDate',
order: 'asc'
}
];
export default class PagesMenu extends Component {
static fragments = List.fragments;
static propTypes = {
children: PropTypes.node,
pages: PropTypes.array.isRequired,
onBack: PropTypes.func.isRequired,
onNew: PropTypes.func.isRequired,
closeNew: PropTypes.func.isRequired,
activePageId: PropTypes.string,
newOpened: PropTypes.bool.isRequired,
sort: PropTypes.string.isRequired,
order: PropTypes.string.isRequired,
search: PropTypes.string.isRequired,
location: PropTypes.object.isRequired
};
render () {
const {pages, onBack, onNew, activePageId, sort, order, location, search} = this.props;
return (
{this.renderNew()}
);
}
renderNew () {
const {newOpened, closeNew} = this.props;
if (newOpened) {
return (
);
}
}
}
================================================
FILE: lib/shared/screens/admin/screens/pages/components/menu.less
================================================
.list {
position: absolute;
top: 110px;
bottom: 0; left: 0; right: 0;
}
================================================
FILE: lib/shared/screens/admin/screens/pages/components/new/index.js
================================================
import Component from 'components/component';
import React, {PropTypes} from 'react';
import {addPage} from 'actions/page';
import New from './new';
export default class NewPageContainer extends Component {
static propTypes = {
fragments: PropTypes.object.isRequired,
onClose: PropTypes.func.isRequired
};
static contextTypes = {
store: PropTypes.object.isRequired
};
getInitState () {
return {
title: '',
loading: false
};
}
changeTitle (title) {
this.setState({
title
});
}
submit () {
if (!this.state.loading) {
this.setState({
loading: true
}, () => {
const {store} = this.context;
const {fragments, onClose} = this.props;
const {title} = this.state;
store.dispatch(addPage(fragments.pages, {title}, true)).then(() => {
onClose && onClose();
});
});
}
}
render () {
return (
);
}
}
================================================
FILE: lib/shared/screens/admin/screens/pages/components/new/new.jsx
================================================
import Component from 'components/component';
import ModalInput from 'components/modal-input';
import ModalNew from 'components/modal-new';
import React, {PropTypes} from 'react';
export default class NewPage extends Component {
static propTypes = {
title: PropTypes.string.isRequired,
changeTitle: PropTypes.func.isRequired,
submit: PropTypes.func.isRequired,
loading: PropTypes.bool
};
render () {
const {title, changeTitle, submit, loading} = this.props;
return (
);
}
}
================================================
FILE: lib/shared/screens/admin/screens/pages/components/pages.jsx
================================================
import Component from 'components/component';
import React, {PropTypes} from 'react';
import styles from './pages.less';
import Menu from './menu';
import New from './new';
export default class Pages extends Component {
static propTypes = {
children: PropTypes.node,
count: PropTypes.number.isRequired
};
render () {
let result;
if (this.props.count === 0) {
result = this.renderNoPages();
} else if (this.props.children) {
result = this.props.children;
} else {
result = this.renderEmpty();
}
return result;
}
renderEmpty () {
return (
Relax, select a page first!
);
}
renderNoPages () {
return (
Oh my!
You don’t have any pages yet!
Lets change that
);
}
}
================================================
FILE: lib/shared/screens/admin/screens/pages/components/pages.less
================================================
@import '~styles/sizes.less';
@import '~styles/colors.less';
.empty {
position: absolute;
top: 45%;
left: 50%;
transform: translate(-50%, -50%);
text-align: center;
:global i {
font-size: 56px;
color: @adminTextSub;
margin-bottom: 20px;
margin-top: 20px;
}
}
.emptyText {
font-size: 17px;
color: @adminTextSub;
}
.noPages {
width: 440px;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
text-align: center;
}
.noTitle {
font-size: 36px;
font-weight: 300;
margin-bottom: 5px;
color: @adminText;
}
.noText {
font-size: 16px;
color: @adminText;
}
================================================
FILE: lib/shared/screens/admin/screens/pages/index.js
================================================
import Component from 'components/component';
import React, {PropTypes} from 'react';
import {dataConnect} from 'relate-js';
import Pages from './components/pages.jsx';
@dataConnect(
() => ({
fragments: {
pagesCount: true
}
})
)
export default class PagesContainer extends Component {
static propTypes = {
children: PropTypes.node,
pagesCount: PropTypes.number
};
render () {
const {pagesCount} = this.props;
return (
{this.props.children}
);
}
}
================================================
FILE: lib/shared/screens/admin/screens/pages/menu.js
================================================
import * as adminMenuActions from 'actions/admin-menu';
import Component from 'components/component';
import React, {PropTypes} from 'react';
import {bindActionCreators} from 'redux';
import {dataConnect} from 'relate-js';
import Menu from './components/menu';
@dataConnect(
(state) => ({
params: state.router.params,
location: state.router.location,
sort: state.router.location.query.sort || '_id',
order: state.router.location.query.order || 'desc',
search: state.router.location.query.s || ''
}),
(dispatch) => bindActionCreators(adminMenuActions, dispatch),
(props) => ({
fragments: Menu.fragments,
variablesTypes: {
pages: {
sort: 'String',
order: 'String',
search: 'String',
s: 'String'
}
},
initialVariables: {
pages: {
sort: props.sort,
order: props.order,
search: 'title',
s: props.search
}
},
mutations: {
addPage: [
{
type: 'PREPEND',
field: 'pages'
}
]
}
})
)
export default class PagesContainer extends Component {
static propTypes = {
pages: PropTypes.array.isRequired,
closeAdminMenu: PropTypes.func.isRequired,
openAdminMenu: PropTypes.func.isRequired,
params: PropTypes.object.isRequired,
sort: PropTypes.string.isRequired,
order: PropTypes.string.isRequired,
search: PropTypes.string.isRequired
};
static defaultProps = {
pages: []
};
getInitState () {
return {
newOpened: false
};
}
componentDidMount () {
this.props.openAdminMenu();
}
componentWillReceiveProps (nextProps) {
if (nextProps.sort !== this.props.sort ||
nextProps.order !== this.props.order ||
nextProps.search !== this.props.search) {
this.props.relate.setVariables({
pages: {
sort: nextProps.sort,
order: nextProps.order,
search: 'title',
s: nextProps.search
}
});
}
}
onBack () {
this.props.closeAdminMenu();
}
onNew () {
this.setState({
newOpened: true
});
}
closeNew () {
this.setState({
newOpened: false
});
}
render () {
const {pages, params, sort, order, location, search} = this.props;
return (
);
}
}
================================================
FILE: lib/shared/screens/admin/screens/pages/screens/page/components/info/index.js
================================================
import bind from 'decorators/bind';
import Component from 'components/component';
import React, {PropTypes} from 'react';
import {removePage, publishPage, unpublishPage} from 'actions/page';
import {bindActionCreators} from 'redux';
import {dataConnect} from 'relate-js';
import PageInfo from './info';
@dataConnect(
(state) => ({
pageId: state.router.params.id
}),
(dispatch) => bindActionCreators({removePage, publishPage, unpublishPage}, dispatch),
(props) => ({
fragments: PageInfo.fragments,
variablesTypes: {
page: {
_id: 'ID!'
}
},
initialVariables: {
page: {
_id: props.pageId
}
}
})
)
export default class PageInfoContainer extends Component {
static propTypes = {
pageId: PropTypes.string.isRequired
};
@bind
onDelete () {
const {pageId} = this.props;
this.props.removePage(pageId, true);
}
@bind
publishPage () {
const {pageId} = this.props;
this.props.publishPage(pageId);
}
@bind
unpublishPage () {
const {pageId} = this.props;
this.props.unpublishPage(pageId);
}
render () {
const {page} = this.props;
return (
);
}
}
================================================
FILE: lib/shared/screens/admin/screens/pages/screens/page/components/info/info.jsx
================================================
import getGravatarImage from 'helpers/get-gravatar-image';
import moment from 'moment';
import Component from 'components/component';
import React, {PropTypes} from 'react';
import styles from './info.less';
import Item from './item';
export default class PageInfo extends Component {
static fragments = {
page: {
_id: 1,
state: 1,
date: 1,
updatedDate: 1,
createdBy: {
_id: 1,
email: 1,
name: 1
},
updatedBy: {
_id: 1,
email: 1,
name: 1
}
}
};
static propTypes = {
page: PropTypes.object,
onDelete: PropTypes.func.isRequired,
publishPage: PropTypes.func.isRequired,
unpublishPage: PropTypes.func.isRequired
};
static defaultProps = {
page: {}
};
render () {
const {page, onDelete} = this.props;
const date = page.date && moment(page.date).format('LL');
const updatedDate = page.updatedDate && moment(page.updatedDate).format('LL');
const createdByUserImage = getGravatarImage(page.createdBy && page.createdBy.email || 'default', 20);
const udpatedByUserImage = getGravatarImage(page.updatedBy && page.updatedBy.email || 'default', 20);
return (
{this.renderStatus()}
);
}
renderStatus () {
const {page} = this.props;
let result;
if (page.state === 'draft') {
return this.renderDraftStatus();
} else if (page.state === 'published') {
return this.renderPublishedStatus();
}
return result;
}
renderDraftStatus () {
const {publishPage} = this.props;
return (
This page is still a draft
);
}
renderPublishedStatus () {
const {unpublishPage} = this.props;
return (
This page is published
);
}
}
================================================
FILE: lib/shared/screens/admin/screens/pages/screens/page/components/info/info.less
================================================
@import '~styles/colors.less';
.root {
padding: 30px 25px;
}
.actionButton {
display: inline-block;
color: @alert;
font-weight: 300;
font-size: 13px;
text-transform: uppercase;
}
.bottom {
position: absolute;
bottom: 0;
left: 0;
right: 0;
padding: 15px 20px;
text-align: center;
border-top: 1px solid @adminBorders;
}
.draft {
margin-bottom: 45px;
text-align: center;
}
.publishButton {
display: inline-block;
:global {
i, span {
display: inline-block;
vertical-align: top;
color: @primary;
line-height: 22px;
font-size: 16px;
}
i {
margin-right: 5px;
}
span {
text-transform: uppercase;
}
}
}
.unpublishButton {
display: inline-block;
:global {
i, span {
display: inline-block;
vertical-align: top;
color: @draft;
line-height: 22px;
font-size: 16px;
}
i {
margin-right: 5px;
}
span {
text-transform: uppercase;
}
}
}
.label {
font-size: 13px;
color: @adminText;
margin-top: 12px;
}
================================================
FILE: lib/shared/screens/admin/screens/pages/screens/page/components/info/item.jsx
================================================
import cx from 'classnames';
import Component from 'components/component';
import React, {PropTypes} from 'react';
import styles from './item.less';
export default class InfoItem extends Component {
static propTypes = {
icon: PropTypes.string,
image: PropTypes.string,
label: PropTypes.string.isRequired,
value: PropTypes.string.isRequired
};
render () {
const {icon, label, value, image} = this.props;
return (
{icon &&
}
{image &&

}
{label}
{value}
);
}
}
================================================
FILE: lib/shared/screens/admin/screens/pages/screens/page/components/info/item.less
================================================
@import '~styles/colors.less';
.root {
margin-bottom: 35px;
}
.icon {
font-size: 14px;
line-height: 20px;
color: @adminText;
width: 20px;
text-align: center;
margin-right: 7px;
}
.image {
display: inline-block;
width: 20px;
height: 20px;
border-radius: 50%;
overflow: hidden;
margin-right: 7px;
}
.infoLabel {
font-size: 13px;
color: @adminTextHighlight;
line-height: 20px;
text-transform: uppercase;
}
.infoValue {
padding-left: 27px;
color: @adminText;
font-size: 13px;
}
================================================
FILE: lib/shared/screens/admin/screens/pages/screens/page/components/page.jsx
================================================
import cx from 'classnames';
import velocity from 'velocity-animate';
import A from 'components/a';
import Animate from 'components/animate';
import Component from 'components/component';
import ContentHeader from 'components/content-header';
import ContentHeaderActions from 'components/content-header-actions';
import ContentLoading from 'components/content-loading';
import ContentSidebar from 'components/content-sidebar';
import EditableTitle from 'components/editable-title';
import PageBuilder from 'components/page-builder';
import React, {PropTypes} from 'react';
import {findDOMNode} from 'react-dom';
import styles from './page.less';
import Info from './info';
import Revisions from './revisions';
export default class Page extends Component {
static fragments = {
page: {
_id: 1,
title: 1,
slug: 1
}
};
static propTypes = {
page: PropTypes.object.isRequired,
location: PropTypes.object.isRequired,
updateTitle: PropTypes.func.isRequired,
updateSlug: PropTypes.func.isRequired,
loading: PropTypes.bool.isRequired,
togglePageInfo: PropTypes.func.isRequired,
togglePageRevisions: PropTypes.func.isRequired,
pageId: PropTypes.string.isRequired,
sidebar: PropTypes.string
};
static defaultProps = {
page: {}
};
getInitState () {
const {location} = this.props;
return {
build: location.query.build && true
};
}
componentWillReceiveProps (nextProps) {
const {location} = this.props;
const oldBuild = location.query.build;
const currentBuild = nextProps.location.query.build;
if (this.props.pageId !== nextProps.pageId) {
this.setState({
build: location.query.build && true
});
}
if (oldBuild !== currentBuild) {
const config = {
duration: 800,
display: null,
easing: 'easeOutExpo'
};
if (currentBuild) {
velocity(this.refs.content, {top: '0px'}, config);
// velocity(findDOMNode(this.refs.header), {translateY: '-70px'}, config);
velocity(findDOMNode(this.refs.cover), {opacity: 0}, Object.assign({}, config, {display: 'none'}));
} else {
velocity(this.refs.content, {top: '70px'}, config);
// velocity(findDOMNode(this.refs.header), {translateY: '0px'}, config);
velocity(findDOMNode(this.refs.cover), {opacity: 1}, Object.assign({}, config, {display: 'block'}));
}
}
}
render () {
const {loading, page} = this.props;
let result;
if (loading) {
result = (
);
} else if (page) {
result = this.renderContent();
} else {
result = ;
}
return result;
}
renderContent () {
const {page, location, updateTitle, updateSlug, togglePageInfo, togglePageRevisions, sidebar} = this.props;
return (
);
}
renderSidebar () {
const {sidebar} = this.props;
const opened = sidebar !== null && !this.props.location.query.build;
return (
{this.renderSidebarContent()}
);
}
renderSidebarContent () {
const {sidebar} = this.props;
let result;
if (sidebar === 'info') {
result = (
);
} else if (sidebar === 'revisions') {
result = (
);
}
return result;
}
}
================================================
FILE: lib/shared/screens/admin/screens/pages/screens/page/components/page.less
================================================
@import '~styles/colors.less';
.info {
display: inline-block;
vertical-align: top;
}
.actionButton {
display: inline-block;
vertical-align: top;
width: 30px;
height: 30px;
margin: 10px 5px;
:global i {
font-size: 22px;
line-height: 30px;
color: @adminText;
}
&.active {
:global i {
color: @primary;
}
}
}
.content {
position: absolute;
top: 70px;
left: 0;
right: 0;
bottom: 0;
overflow: hidden;
background-color: #ffffff;
}
.cover {
cursor: pointer;
display: block;
position: absolute;
left: 0; top: 0; right: 0; bottom: 0;
background-color: rgba(0, 0, 0, 0.6);
z-index: 10;
transition: background-color 0.4s ease-out;
&:hover {
background-color: rgba(0, 0, 0, 0.7);
}
}
.coverContent {
display: inline-block;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
text-align: center;
border: 2px dotted #ffffff;
width: 176px;
height: 176px;
border-radius: 50%;
padding: 35px 0;
:global {
i, div {
display: block;
color: #ffffff;
}
i {
font-size: 60px;
}
div {
margin-top: 20px;
font-size: 16px;
}
}
}
.build {
.cover {
opacity: 0;
display: none;
}
.content {
top: 0;
}
}
================================================
FILE: lib/shared/screens/admin/screens/pages/screens/page/components/revisions/index.js
================================================
import Component from 'components/component';
import React from 'react';
export default class PageRevisionsContainer extends Component {
render () {
return Page Revisions
;
}
}
================================================
FILE: lib/shared/screens/admin/screens/pages/screens/page/index.js
================================================
import bind from 'decorators/bind';
import Component from 'components/component';
import React, {PropTypes} from 'react';
import {updatePageTitle, updatePageSlug} from 'actions/page';
import {addTab} from 'actions/tabs';
import {bindActionCreators} from 'redux';
import {dataConnect} from 'relate-js';
import Page from './components/page';
@dataConnect(
(state) => ({
pageId: state.router.params.id,
location: state.router.location
}),
(dispatch) => bindActionCreators({updatePageTitle, updatePageSlug, addTab}, dispatch),
(props) => ({
fragments: Page.fragments,
variablesTypes: {
page: {
_id: 'ID!'
}
},
initialVariables: {
page: {
_id: props.pageId
}
}
})
)
export default class PageContainer extends Component {
static propTypes = {
relate: PropTypes.object.isRequired,
page: PropTypes.object.isRequired,
pageId: PropTypes.string.isRequired,
location: PropTypes.object.isRequired,
updatePage: PropTypes.func.isRequired
};
getInitState () {
this.processTab(this.props);
return {
sidebar: null
};
}
componentWillReceiveProps (nextProps) {
if (this.props.pageId !== nextProps.pageId) {
this.setState({
sidebar: null
});
this.props.relate.setVariables({
page: {
_id: nextProps.pageId
}
});
}
const oldBuild = this.props.location.query.build;
const currentBuild = nextProps.location.query.build;
if (oldBuild !== currentBuild || this.props.pageId !== nextProps.pageId) {
this.processTab(nextProps);
}
}
processTab (props) {
const currentBuild = props.location.query.build;
if (currentBuild) {
this.props.addTab('page', props.pageId);
}
}
@bind
updateTitle (title) {
const {page} = this.props;
return this.props.updatePageTitle(page._id, title);
}
@bind
updateSlug (slug) {
const {page} = this.props;
return this.props.updatePageSlug(page._id, slug);
}
@bind
togglePageInfo () {
const {sidebar} = this.state;
this.setState({
sidebar: sidebar === 'info' ? null : 'info'
});
}
@bind
togglePageRevisions () {
const {sidebar} = this.state;
this.setState({
sidebar: sidebar === 'revisions' ? null : 'revisions'
});
}
render () {
const {page, location, loading, pageId} = this.props;
return (
);
}
}
================================================
FILE: lib/shared/screens/admin/screens/schemas/screens/new/components/builder/builder.jsx
================================================
import Component from 'components/component';
import React, {PropTypes} from 'react';
import Model from './model';
import Name from './name';
import Types from './types';
const steps = [
Types,
Name,
Model
];
export default class SchemaBuilder extends Component {
static propTypes = {
step: PropTypes.number.isRequired
};
render () {
const {step} = this.props;
const StepComponent = steps[step];
return (
);
}
}
================================================
FILE: lib/shared/screens/admin/screens/schemas/screens/new/components/builder/index.js
================================================
import * as schemaActions from 'actions/schema';
import Component from 'components/component';
import React from 'react';
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
import Builder from './builder';
@connect(
(state) => ({
step: state.schema.step,
schema: state.schema.data
}),
(dispatch) => bindActionCreators(schemaActions, dispatch)
)
export default class SchemasBuilder extends Component {
render () {
return (
);
}
}
================================================
FILE: lib/shared/screens/admin/screens/schemas/screens/new/components/builder/model.jsx
================================================
import bind from 'decorators/bind';
import cx from 'classnames';
import Component from 'components/component';
import Scrollable from 'components/scrollable';
import React, {PropTypes} from 'react';
import styles from './model.less';
import Properties from './properties';
export default class SchemaModel extends Component {
static propTypes = {
schema: PropTypes.object.isRequired,
schemaStepBack: PropTypes.func.isRequired,
schemaStepForward: PropTypes.func.isRequired,
addSchema: PropTypes.func.isRequired
};
@bind
onDone () {
const {schema, addSchema} = this.props;
addSchema(schema);
}
render () {
const {schema, schemaStepBack} = this.props;
return (
{this.renderSchemaType(schema.type)}
...and now create the content model.
{`What properties will ${schema.title} single contain?`}
);
}
renderSchemaType (type) {
const {schema} = this.props;
let result;
if (type === 'single') {
result = (
);
} else if (type === 'data') {
result = (
);
}
return result;
}
}
================================================
FILE: lib/shared/screens/admin/screens/schemas/screens/new/components/builder/model.less
================================================
@import '~styles/colors.less';
.root {
text-align: center;
padding: 30px;
width: 800px;
max-width: 100%;
padding-bottom: 40px;
margin: 0 auto;
}
.header {
font-size: 36px;
color: @adminText;
}
.subHeader {
font-size: 16px;
color: @adminText;
}
.option {
display: inline-block;
vertical-align: top;
margin-top: 40px;
margin-bottom: 70px;
:global i {
color: @adminText;
font-size: 40px;
margin-bottom: 10px;
}
}
.title {
font-size: 26px;
color: @primary;
}
.buttons {
margin: 40px 0;
}
.button {
font-size: 16px;
text-transform: uppercase;
color: @adminText;
margin: 0 10px;
}
.primary {
color: @primary;
}
================================================
FILE: lib/shared/screens/admin/screens/schemas/screens/new/components/builder/name.jsx
================================================
import cx from 'classnames';
import Component from 'components/component';
import Input from 'components/modal-input';
import React, {PropTypes} from 'react';
import styles from './name.less';
export default class SchemaName extends Component {
static propTypes = {
schema: PropTypes.object.isRequired,
schemaStepBack: PropTypes.func.isRequired,
schemaStepForward: PropTypes.func.isRequired,
changeSchemaTitle: PropTypes.func.isRequired
};
render () {
const {schema, schemaStepBack, schemaStepForward, changeSchemaTitle} = this.props;
return (
{this.renderSchemaType(schema.type)}
);
}
renderSchemaType (type) {
let result;
if (type === 'single') {
result = (
);
} else if (type === 'data') {
result = (
Without URL
Multiple Table Entries
);
}
return result;
}
}
================================================
FILE: lib/shared/screens/admin/screens/schemas/screens/new/components/builder/name.less
================================================
@import '~styles/colors.less';
.root {
text-align: center;
padding: 30px;
position: absolute;
width: 100%;
left: 50%;
transform: translate(-50%, 0%);
}
.holder {
display: inline-block;
width: 330px;
padding-bottom: 40px;
}
.header {
font-size: 36px;
margin-bottom: 30px;
color: @adminText;
}
.option {
display: inline-block;
vertical-align: top;
width: 180px;
height: 180px;
border-radius: 50%;
border: 1px solid @adminTextSub;
margin-top: 40px;
margin-bottom: 60px;
:global i {
color: @adminText;
font-size: 40px;
margin-bottom: 10px;
margin-top: 45px;
}
}
.title {
font-size: 16px;
font-weight: 600;
color: @adminText;
}
.subTitle {
font-size: 12px;
color: @adminTextSub;
}
.buttons {
margin: 40px 0;
}
.button {
font-size: 16px;
text-transform: uppercase;
color: @adminText;
margin: 0 10px;
}
.primary {
color: @primary;
}
================================================
FILE: lib/shared/screens/admin/screens/schemas/screens/new/components/builder/properties/index.js
================================================
import * as schemaActions from 'actions/schema';
import Component from 'components/component';
import React from 'react';
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
import Properties from './properties';
@connect(
(state) => ({
openedProperties: state.schema.openedProperties,
properties: state.schema.data.properties
}),
(dispatch) => bindActionCreators(schemaActions, dispatch)
)
export default class SchemasBuilderPropertiesContainer extends Component {
render () {
return (
);
}
}
================================================
FILE: lib/shared/screens/admin/screens/schemas/screens/new/components/builder/properties/properties.jsx
================================================
import Component from 'components/component';
import React, {PropTypes} from 'react';
import {singleFixedProperties} from 'helpers/data-types';
import styles from './properties.less';
import Property from './property';
export default class SchemaProperties extends Component {
static propTypes = {
openedProperties: PropTypes.array.isRequired,
properties: PropTypes.array.isRequired,
addProperty: PropTypes.func.isRequired,
toggleProperty: PropTypes.func.isRequired,
changePropertySetting: PropTypes.func.isRequired
};
render () {
const {properties, addProperty} = this.props;
return (
{singleFixedProperties.map(this.renderProperty, this)}
{properties.map(this.renderProperty, this)}
);
}
renderProperty (property, key) {
const {openedProperties, toggleProperty, changePropertySetting} = this.props;
return (
);
}
}
================================================
FILE: lib/shared/screens/admin/screens/schemas/screens/new/components/builder/properties/properties.less
================================================
@import '~styles/colors.less';
.root {
margin-top: 50px;
}
.addNew {
text-transform: uppercase;
font-size: 14px;
color: @adminText;
display: block;
width: 100%;
text-align: center;
line-height: 70px;
&:hover {
color: @primary;
}
}
================================================
FILE: lib/shared/screens/admin/screens/schemas/screens/new/components/builder/properties/property-options.jsx
================================================
import bind from 'decorators/bind';
import Component from 'components/component';
import OptionsList from 'components/options-list';
import React, {PropTypes} from 'react';
import {propertyOptions} from 'helpers/data-types';
import styles from './property-options.less';
export default class SchemaPropertyOptions extends Component {
static propTypes = {
property: PropTypes.object.isRequired,
changePropertySetting: PropTypes.func.isRequired
};
@bind
onChange (id, value) {
const {changePropertySetting, property} = this.props;
changePropertySetting(property.id, id, value);
}
render () {
const {property} = this.props;
return (
);
}
}
================================================
FILE: lib/shared/screens/admin/screens/schemas/screens/new/components/builder/properties/property-options.less
================================================
.root {
text-align: left;
padding: 20px;
}
================================================
FILE: lib/shared/screens/admin/screens/schemas/screens/new/components/builder/properties/property.jsx
================================================
import bind from 'decorators/bind';
import cx from 'classnames';
import Component from 'components/component';
import React, {PropTypes} from 'react';
import styles from './property.less';
import PropertyOptions from './property-options';
export default class SchemaProperty extends Component {
static propTypes = {
property: PropTypes.object.isRequired,
opened: PropTypes.bool.isRequired,
toggleProperty: PropTypes.func.isRequired,
changePropertySetting: PropTypes.func.isRequired
};
@bind
onToggle () {
const {toggleProperty, property} = this.props;
toggleProperty(property.id);
}
render () {
const {property} = this.props;
return (
{property.title}
{property.id}
{property.type}
{this.renderIcon()}
{this.renderOpened()}
);
}
renderIcon () {
const {opened, property} = this.props;
let result;
if (property.locked) {
result = (
);
} else {
result = (
);
}
return result;
}
renderOpened () {
const {opened, property} = this.props;
if (opened && !property.locked) {
const {changePropertySetting} = this.props;
return (
);
}
}
}
================================================
FILE: lib/shared/screens/admin/screens/schemas/screens/new/components/builder/properties/property.less
================================================
@import '~styles/colors.less';
.root {
border: 1px solid @adminBorders;
margin-top: -1px;
}
.header {
padding: 15px 25px;
height: 70px;
text-align: left;
cursor: pointer;
&:hover {
background-color: #fefefe;
}
}
.locked .header {
cursor: default;
&:hover {
background-color: fadeout(@alert, 99%);
}
}
.info {
display: inline-block;
vertical-align: top;
float: left;
}
.title {
font-size: 15px;
line-height: 22px;
color: @adminTextHighlight;
}
.id {
font-size: 12px;
color: @adminText;
}
.right {
display: inline-block;
vertical-align: top;
float: right;
}
.icon, .type {
display: inline-block;
vertical-align: top;
line-height: 40px;
}
.icon {
color: @adminText;
font-size: 10px;
margin-left: 10px;
}
.locked .icon {
color: fadeout(@alert, 60%);
}
.type {
font-size: 15px;
color: @adminText;
}
================================================
FILE: lib/shared/screens/admin/screens/schemas/screens/new/components/builder/type.jsx
================================================
import bind from 'decorators/bind';
import Component from 'components/component';
import React, {PropTypes} from 'react';
import styles from './type.less';
export default class SchemaType extends Component {
static propTypes = {
changeSchemaType: PropTypes.func.isRequired,
type: PropTypes.string.isRequired,
image: PropTypes.string.isRequired,
title: PropTypes.string.isRequired,
subTitle: PropTypes.string.isRequired
};
@bind
onClick () {
const {changeSchemaType, type} = this.props;
changeSchemaType(type);
}
render () {
const {image, title, subTitle} = this.props;
return (
);
}
}
================================================
FILE: lib/shared/screens/admin/screens/schemas/screens/new/components/builder/type.less
================================================
@import '~styles/colors.less';
.option {
display: inline-block;
vertical-align: top;
width: 320px;
height: 320px;
border-radius: 50%;
border: 1px solid @adminTextSub;
&:hover {
border-color: @primary;
}
}
.icon {
margin-bottom: 20px;
}
.title {
font-size: 36px;
font-weight: 600;
color: @adminText;
}
.subTitle {
font-size: 16px;
color: @adminTextSub;
}
================================================
FILE: lib/shared/screens/admin/screens/schemas/screens/new/components/builder/types.jsx
================================================
import Component from 'components/component';
import React, {PropTypes} from 'react';
import styles from './types.less';
import Type from './type';
export default class SchemaTypes extends Component {
static propTypes = {
changeSchemaType: PropTypes.func.isRequired
};
render () {
const {changeSchemaType} = this.props;
return (
Let's create some new content types!
What type of content is this?
);
}
}
================================================
FILE: lib/shared/screens/admin/screens/schemas/screens/new/components/builder/types.less
================================================
@import '~styles/colors.less';
.root {
text-align: center;
padding: 30px;
position: absolute;
width: 100%;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
.subHeader {
font-size: 16px;
color: @adminText;
}
.header {
font-size: 36px;
color: @primary;
font-weight: 300;
}
.options {
margin-top: 80px;
}
.or {
display: inline-block;
vertical-align: top;
line-height: 320px;
font-size: 26px;
font-weight: 600;
color: @adminText;
padding: 0 50px;
}
================================================
FILE: lib/shared/screens/admin/screens/schemas/screens/new/index.js
================================================
import Component from 'components/component';
import React from 'react';
import Builder from './components/builder';
export default class SchemasNewContainer extends Component {
render () {
return (
);
}
}
================================================
FILE: lib/shared/screens/admin/screens/schemas/screens/schema/components/entry.jsx
================================================
import cx from 'classnames';
import moment from 'moment';
import Component from 'components/component';
import React, {PropTypes} from 'react';
import {Link} from 'react-router';
import styles from './entry.less';
export default class SchemaEntry extends Component {
static fragments = {
schemaEntry: {
_id: 1,
title: 1,
state: 1,
date: 1
}
};
static propTypes = {
schemaEntry: PropTypes.object.isRequired,
active: PropTypes.bool.isRequired,
query: PropTypes.object.isRequired
};
render () {
const {schemaEntry, active, query} = this.props;
const date = moment(schemaEntry.date).fromNow();
const editLink = '/admin/schemas/entry/' + schemaEntry._id; // TODO fix
return (
{schemaEntry.title}
{date}
);
}
}
================================================
FILE: lib/shared/screens/admin/screens/schemas/screens/schema/components/entry.less
================================================
@import '~styles/colors.less';
.root {
height: 70px;
border-bottom: 1px solid @adminBorders;
padding: 13px;
position: relative;
display: block;
text-decoration: none;
&:hover {
background-color: @primaryBack;
}
}
.status {
position: absolute;
left: 13px;
top: 30px;
width: 10px;
height: 10px;
background-color: #F7CF00;
border-radius: 50%;
}
.published {
background-color: @primary;
}
.info {
padding-left: 25px;
padding-top: 3px;
}
.title {
font-size: 15px;
color: @adminTextHighlight;
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
}
.date {
font-size: 12px;
color: @adminText;
font-weight: 300;
}
.active {
background-color: @primary;
.status.published {
background-color: #ffffff;
}
.title {
color: #ffffff;
}
.date {
color: #ffffff;
}
&:hover {
background-color: @primary;
}
}
================================================
FILE: lib/shared/screens/admin/screens/schemas/screens/schema/components/list.jsx
================================================
import Component from 'components/component';
import React, {PropTypes} from 'react';
import Entry from './entry';
export default class SchemaEntriesList extends Component {
static fragments = {
schemaList: Entry.fragments.schemaEntry
};
static propTypes = {
schemaList: PropTypes.array.isRequired,
activeSchemaEntryId: PropTypes.string,
query: PropTypes.object.isRequired
};
render () {
return (
{this.props.schemaList.map(this.renderEntry, this)}
);
}
renderEntry (schemaEntry, key) {
const {activeSchemaEntryId, query} = this.props;
return (
);
}
}
================================================
FILE: lib/shared/screens/admin/screens/schemas/screens/schema/components/menu.jsx
================================================
import Component from 'components/component';
import ListHeader from 'components/list-header';
import ListSearchSort from 'components/list-search-sort';
import Modal from 'components/modal';
import Scrollable from 'components/scrollable';
import React, {PropTypes} from 'react';
import {mergeFragments} from 'relate-js';
import styles from './menu.less';
import List from './list';
import New from './new';
const sorts = [
{
label: 'Date desc',
sort: '_id',
order: 'desc'
},
{
label: 'Date asc',
sort: '_id',
order: 'asc'
},
{
label: 'Title A-Z',
sort: 'title',
order: 'asc'
},
{
label: 'Title Z-A',
sort: 'title',
order: 'desc'
},
{
label: 'Updated desc',
sort: 'updatedDate',
order: 'desc'
},
{
label: 'Updated asc',
sort: 'updatedDate',
order: 'asc'
}
];
export default class SchemaMenu extends Component {
static fragments = mergeFragments(
List.fragments,
{
schema: {
_id: 1,
title: 1
}
}
);
static propTypes = {
schemaList: PropTypes.array.isRequired,
schema: PropTypes.object.isRequired,
onBack: PropTypes.func.isRequired,
onNew: PropTypes.func.isRequired,
closeNew: PropTypes.func.isRequired,
activeSchemaEntryId: PropTypes.string,
newOpened: PropTypes.bool.isRequired,
sort: PropTypes.string.isRequired,
order: PropTypes.string.isRequired,
search: PropTypes.string.isRequired,
location: PropTypes.object.isRequired
};
render () {
const {schemaList, schema, onBack, onNew, activeSchemaEntryId, sort, order, location, search} = this.props;
return (
{this.renderNew()}
);
}
renderNew () {
const {newOpened, closeNew, schema} = this.props;
if (newOpened) {
return (
);
}
}
}
================================================
FILE: lib/shared/screens/admin/screens/schemas/screens/schema/components/menu.less
================================================
.list {
position: absolute;
top: 110px;
bottom: 0; left: 0; right: 0;
}
================================================
FILE: lib/shared/screens/admin/screens/schemas/screens/schema/components/new/index.js
================================================
import bind from 'decorators/bind';
import Component from 'components/component';
import React, {PropTypes} from 'react';
import {addSchemaEntry} from 'actions/schema-entry';
import New from './new';
export default class NewSchemaEntryContainer extends Component {
static propTypes = {
onClose: PropTypes.func.isRequired,
schemaId: PropTypes.string.isRequired
};
static contextTypes = {
store: PropTypes.object.isRequired
};
getInitState () {
return {
title: '',
loading: false
};
}
@bind
changeTitle (title) {
this.setState({
title
});
}
@bind
submit () {
if (!this.state.loading) {
this.setState({
loading: true
}, () => {
const {store} = this.context;
const {onClose, schemaId} = this.props;
const {title} = this.state;
store.dispatch(addSchemaEntry(schemaId, {title}, true)).then(() => {
onClose && onClose();
});
});
}
}
render () {
return (
);
}
}
================================================
FILE: lib/shared/screens/admin/screens/schemas/screens/schema/components/new/new.jsx
================================================
import Component from 'components/component';
import ModalInput from 'components/modal-input';
import ModalNew from 'components/modal-new';
import React, {PropTypes} from 'react';
export default class NewSchemaEntry extends Component {
static propTypes = {
title: PropTypes.string.isRequired,
changeTitle: PropTypes.func.isRequired,
submit: PropTypes.func.isRequired,
loading: PropTypes.bool
};
render () {
const {title, changeTitle, submit, loading} = this.props;
return (
);
}
}
================================================
FILE: lib/shared/screens/admin/screens/schemas/screens/schema/components/schema.jsx
================================================
import Component from 'components/component';
import React, {PropTypes} from 'react';
import styles from './schema.less';
import New from './new';
export default class Schema extends Component {
static propTypes = {
children: PropTypes.node,
count: PropTypes.number.isRequired,
schemaId: PropTypes.string.isRequired
};
render () {
let result;
if (this.props.count === 0) {
result = this.renderNoPages();
} else if (this.props.children) {
result = this.props.children;
} else {
result = this.renderEmpty();
}
return result;
}
renderEmpty () {
return (
Relax, select an entry first!
);
}
renderNoPages () {
const {schemaId} = this.props;
return (
Oh my!
You don’t have any entries yet!
Lets change that
);
}
}
================================================
FILE: lib/shared/screens/admin/screens/schemas/screens/schema/components/schema.less
================================================
@import '~styles/sizes.less';
@import '~styles/colors.less';
.empty {
position: absolute;
top: 45%;
left: 50%;
transform: translate(-50%, -50%);
text-align: center;
:global i {
font-size: 56px;
color: @adminTextSub;
margin-bottom: 20px;
margin-top: 20px;
}
}
.emptyText {
font-size: 17px;
color: @adminTextSub;
}
.noEntries {
width: 440px;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
text-align: center;
}
.noTitle {
font-size: 36px;
font-weight: 300;
margin-bottom: 5px;
color: @adminText;
}
.noText {
font-size: 16px;
color: @adminText;
}
================================================
FILE: lib/shared/screens/admin/screens/schemas/screens/schema/index.js
================================================
import Component from 'components/component';
import React, {PropTypes} from 'react';
import {dataConnect} from 'relate-js';
import Schema from './components/schema';
@dataConnect(
(state) => ({
schemaId: state.router.params.id
}),
(props) => ({
fragments: {
schemaListCount: true
},
variablesTypes: {
schemaListCount: {
schemaId: 'ID!'
}
},
initialVariables: {
schemaListCount: {
schemaId: props.schemaId
}
},
mutations: {
addSchemaEntry: [
{
type: 'INCREMENT',
field: 'schemaListCount'
}
]
}
})
)
export default class SchemaContainer extends Component {
static propTypes = {
children: PropTypes.node,
schemaId: PropTypes.string.isRequired
};
render () {
const {schemaListCount, schemaId} = this.props;
return (
{this.props.children}
);
}
}
================================================
FILE: lib/shared/screens/admin/screens/schemas/screens/schema/menu.js
================================================
import * as adminMenuActions from 'actions/admin-menu';
import bind from 'decorators/bind';
import Component from 'components/component';
import React, {PropTypes} from 'react';
import {bindActionCreators} from 'redux';
import {dataConnect} from 'relate-js';
import Menu from './components/menu';
@dataConnect(
(state) => ({
params: state.router.params,
location: state.router.location,
sort: state.router.location.query.sort || '_id',
order: state.router.location.query.order || 'desc',
search: state.router.location.query.s || ''
}),
(dispatch) => bindActionCreators(adminMenuActions, dispatch),
(props) => ({
fragments: Menu.fragments,
variablesTypes: {
schema: {
_id: 'ID!'
},
schemaList: {
schemaId: 'ID!',
sort: 'String',
order: 'String',
search: 'String',
s: 'String'
}
},
initialVariables: {
schema: {
_id: props.params.id
},
schemaList: {
schemaId: props.params.id,
sort: props.sort,
order: props.order,
search: 'title',
s: props.search
}
},
mutations: {
addSchemaEntry: [
{
type: 'PREPEND',
field: 'schemaList'
}
]
}
})
)
export default class SchemaMenuContainer extends Component {
static propTypes = {
children: PropTypes.node,
schemaList: PropTypes.array,
schema: PropTypes.object,
closeAdminMenu: PropTypes.func.isRequired,
openAdminMenu: PropTypes.func.isRequired,
params: PropTypes.object.isRequired,
sort: PropTypes.string.isRequired,
order: PropTypes.string.isRequired,
search: PropTypes.string.isRequired
};
static defaultProps = {
schemaList: [],
schema: {}
};
getInitState () {
return {
newOpened: false
};
}
componentDidMount () {
this.props.openAdminMenu();
}
componentWillReceiveProps (nextProps) {
if (nextProps.sort !== this.props.sort ||
nextProps.order !== this.props.order ||
nextProps.search !== this.props.search) {
this.props.relate.setVariables({
schema: {
_id: nextProps.params.id
},
schemaList: {
schemaId: nextProps.params.id,
sort: nextProps.sort,
order: nextProps.order,
search: 'title',
s: nextProps.search
}
});
}
}
@bind
onBack () {
this.props.closeAdminMenu();
}
@bind
onNew () {
this.setState({
newOpened: true
});
}
@bind
closeNew () {
this.setState({
newOpened: false
});
}
render () {
const {schemaList, schema, params, sort, order, search, location} = this.props;
return (
);
}
}
================================================
FILE: lib/shared/screens/admin/screens/settings/components/menu.jsx
================================================
import Button from 'components/menu-button';
import Component from 'components/component';
import ListHeader from 'components/list-header';
import Scrollable from 'components/scrollable';
import React, {PropTypes} from 'react';
import styles from './menu.less';
export default class SettingsMenu extends Component {
static propTypes = {
onBack: PropTypes.func.isRequired,
pathname: PropTypes.string.isRequired
};
render () {
const {onBack, pathname} = this.props;
return (
);
}
}
================================================
FILE: lib/shared/screens/admin/screens/settings/components/menu.less
================================================
.list {
position: absolute;
top: 70px;
bottom: 0; left: 0; right: 0;
}
================================================
FILE: lib/shared/screens/admin/screens/settings/menu.js
================================================
import * as adminMenuActions from 'actions/admin-menu';
import bind from 'decorators/bind';
import Component from 'components/component';
import React, {PropTypes} from 'react';
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
import Menu from './components/menu';
@connect(
(state) => ({
pathname: state.router.location.pathname
}),
(dispatch) => bindActionCreators(adminMenuActions, dispatch)
)
export default class SettingsMenuContainer extends Component {
static propTypes = {
closeAdminMenu: PropTypes.func.isRequired,
openAdminMenu: PropTypes.func.isRequired,
pathname: PropTypes.string.isRequired
};
componentDidMount () {
this.props.openAdminMenu();
}
@bind
onBack () {
this.props.closeAdminMenu();
}
render () {
const {pathname} = this.props;
return (
);
}
}
================================================
FILE: lib/shared/screens/admin/screens/settings/screens/analytics/components/analytics.jsx
================================================
import Component from 'components/component';
import OptionsList from 'components/options-list';
import SettingsContent from 'components/settings-content';
import React, {PropTypes} from 'react';
export default class AnalyticsSettings extends Component {
static propTypes = {
options: PropTypes.array.isRequired
};
render () {
const {options} = this.props;
return (
);
}
}
================================================
FILE: lib/shared/screens/admin/screens/settings/screens/analytics/index.js
================================================
import Component from 'components/component';
import React, {PropTypes} from 'react';
import AnalyticsSettings from './components/analytics';
const options = [
{
label: 'Google analytics tracking ID',
type: 'String',
id: 'googleAnalytics',
props: {
placeholder: 'UA-XXXXX-Y'
}
}
];
export default class AnalyticsSettingsContainer extends Component {
static propTypes = {};
render () {
return (
);
}
}
================================================
FILE: lib/shared/screens/admin/screens/settings/screens/data/components/data.jsx
================================================
import Component from 'components/component';
import React, {PropTypes} from 'react';
export default class DataSettings extends Component {
static propTypes = {};
render () {
return (
Data Settings
);
}
}
================================================
FILE: lib/shared/screens/admin/screens/settings/screens/data/index.js
================================================
import Component from 'components/component';
import React, {PropTypes} from 'react';
import DataSettings from './components/data';
export default class DataSettingsContainer extends Component {
static propTypes = {};
render () {
return (
);
}
}
================================================
FILE: lib/shared/screens/admin/screens/settings/screens/email/components/email.jsx
================================================
import Component from 'components/component';
import OptionsList from 'components/options-list';
import SettingsContent from 'components/settings-content';
import React, {PropTypes} from 'react';
export default class EmailSettings extends Component {
static propTypes = {
options: PropTypes.array.isRequired
};
render () {
const {options} = this.props;
return (
);
}
}
================================================
FILE: lib/shared/screens/admin/screens/settings/screens/email/index.js
================================================
import Component from 'components/component';
import React, {PropTypes} from 'react';
import EmailSettings from './components/email';
const options = [
{
label: 'Mail service',
type: 'Select',
id: 'mailService',
props: {
values: [
'1und1',
'AOL',
'DebugMail.io',
'DynectEmail',
'FastMail',
'GandiMail',
'Gmail',
'Godaddy',
'GodaddyAsia',
'GodaddyEurope',
'hot.ee',
'Hotmail',
'iCloud',
'mail.ee',
'Mail.ru',
'Mailgun',
'Mailjet',
'Mandrill',
'Naver',
'Postmark',
'QQ',
'QQex',
'SendCloud',
'SendGrid',
'SES',
'Sparkpost',
'Yahoo',
'Yandex',
'Zoho'
]
}
},
{
label: 'Mail user/email',
type: 'String',
id: 'mailUser'
},
{
label: 'Mail user password',
type: 'String',
id: 'mailPass',
props: {
password: true
}
},
{
label: 'Send emails to',
type: 'String',
id: 'mailTo'
}
];
export default class EmailSettingsContainer extends Component {
static propTypes = {};
render () {
return (
);
}
}
================================================
FILE: lib/shared/screens/admin/screens/settings/screens/general/components/general.jsx
================================================
import Component from 'components/component';
import OptionsList from 'components/options-list';
import SettingsContent from 'components/settings-content';
import React, {PropTypes} from 'react';
export default class GeneralSettings extends Component {
static propTypes = {
options: PropTypes.array.isRequired
};
render () {
const {options} = this.props;
return (
);
}
}
================================================
FILE: lib/shared/screens/admin/screens/settings/screens/general/index.js
================================================
import Component from 'components/component';
import React, {PropTypes} from 'react';
import GeneralSettings from './components/general';
const options = [
{
label: 'Site Title',
type: 'String',
id: 'title',
default: ''
},
// {
// label: 'Frontpage',
// type: 'PagePicker',
// id: 'frontpage'
// },
// {
// label: 'Favicon',
// type: 'Image',
// id: 'favicon',
// props: {
// width: 50,
// height: 50,
// type: 'favicon'
// }
// },
// {
// label: 'Webclip',
// type: 'Image',
// id: 'webclip',
// props: {
// width: 114,
// height: 114
// }
// }
];
export default class GeneralSettingsContainer extends Component {
static propTypes = {};
render () {
return (
);
}
}
================================================
FILE: lib/shared/screens/admin/screens/settings/shared/components/settings-content/index.jsx
================================================
import Component from 'components/component';
import Scrollable from 'components/scrollable';
import React, {PropTypes} from 'react';
import styles from './index.less';
export default class SettingsContent extends Component {
static propTypes = {
children: PropTypes.node.isRequired
};
render () {
return (
{this.props.children}
);
}
}
================================================
FILE: lib/shared/screens/admin/screens/settings/shared/components/settings-content/index.less
================================================
.root {
position: absolute;
top: 0px;
left: 0;
right: 0;
bottom: 0;
}
.content {
padding: 50px;
}
================================================
FILE: lib/shared/screens/admin/screens/users/components/entry.jsx
================================================
import bind from 'decorators/bind';
import cx from 'classnames';
import getGravatarImage from 'helpers/get-gravatar-image';
import Component from 'components/component';
import React, {PropTypes} from 'react';
import styles from './entry.less';
export default class Entry extends Component {
static fragments = {
user: {
_id: 1,
name: 1,
email: 1
}
};
static propTypes = {
user: PropTypes.object.isRequired,
onDelete: PropTypes.func.isRequired
};
@bind
onDeleteClick () {
const {user, onDelete} = this.props;
onDelete(user);
}
render () {
const {user} = this.props;
const url = getGravatarImage(user.email, 125);
return (
);
}
}
================================================
FILE: lib/shared/screens/admin/screens/users/components/entry.less
================================================
@import '~styles/colors.less';
.root {
width: 21.25%;
margin-right: 5%;
display: inline-block;
vertical-align: top;
margin-bottom: 60px;
border: 1px solid transparent;
text-align: center;
}
@media screen and (min-width: 1501px) {
.root {
width: 16%;
&:nth-child(5n+5) {
margin-right: 0;
}
}
}
@media screen and (max-width: 1500px) and (min-width: 1101px) {
.root {
&:nth-child(4n+4) {
margin-right: 0;
}
}
}
@media screen and (max-width: 1100px) and (min-width: 851px) {
.root {
width: 30%;
&:nth-child(3n+3) {
margin-right: 0;
}
}
}
@media screen and (max-width: 850px) and (min-width: 751px) {
.root {
width: 45%;
margin-right: 10%;
&:nth-child(2n+2) {
margin-right: 0;
}
}
}
@media screen and (max-width: 750px) {
.root {
width: 100%;
margin-right: 0%;
}
}
.user {
width: 125px;
height: 125px;
display: inline-block;
vertical-align: top;
border-radius: 50%;
overflow: hidden;
margin-top: 10px;
}
.info {
padding: 15px;
height: 65px;
text-align: center;
}
.title {
color: @adminTextHighlight;
font-size: 16px;
line-height: 19px;
}
.value {
color: @adminText;
font-size: 14px;
font-weight: 300;
}
.actions {
visibility: hidden;
border-top: 1px solid @adminBorders;
}
.button {
height: 39px;
line-height: 39px;
font-size: 12px;
text-transform: uppercase;
color: @adminText;
}
.remove {
color: @alert;
}
.root:hover {
border: 1px solid @adminBorders;
.actions {
visibility: visible;
}
}
================================================
FILE: lib/shared/screens/admin/screens/users/components/list.jsx
================================================
import Component from 'components/component';
import React, {PropTypes} from 'react';
import Entry from './entry';
export default class List extends Component {
static fragments = {
users: Entry.fragments.user
};
static propTypes = {
users: PropTypes.array.isRequired,
onDelete: PropTypes.func.isRequired,
search: PropTypes.string.isRequired
};
render () {
const {users} = this.props;
return (
{users.map(this.renderEntry, this)}
);
}
renderEntry (user) {
const {onDelete, search} = this.props;
const inSearch = !search || user.name.toLowerCase().indexOf(search.toLowerCase()) !== -1;
if (inSearch) {
return (
);
}
}
}
================================================
FILE: lib/shared/screens/admin/screens/users/components/new/index.js
================================================
import Component from 'components/component';
import React, {PropTypes} from 'react';
import {addUser} from 'actions/users';
import New from './new';
export default class NewUserContainer extends Component {
static propTypes = {
fragments: PropTypes.object.isRequired,
onClose: PropTypes.func.isRequired
};
static contextTypes = {
store: PropTypes.object.isRequired
};
getInitState () {
return {
username: '',
password: '',
email: '',
name: '',
loading: false
};
}
submit () {
if (!this.state.loading) {
this.setState({
loading: true
}, () => {
const {store} = this.context;
const {fragments, onClose} = this.props;
const {username, password, email, name} = this.state;
store.dispatch(addUser(fragments, {username, password, email, name}, true)).then(() => {
onClose && onClose();
});
});
}
}
changeField (field, value) {
this.setState({
[field]: value
});
}
render () {
return (
);
}
}
================================================
FILE: lib/shared/screens/admin/screens/users/components/new/new.jsx
================================================
import Component from 'components/component';
import ModalInput from 'components/modal-input';
import ModalNew from 'components/modal-new';
import React, {PropTypes} from 'react';
export default class NewUser extends Component {
static propTypes = {
username: PropTypes.string.isRequired,
password: PropTypes.string.isRequired,
name: PropTypes.string.isRequired,
email: PropTypes.string.isRequired,
changeField: PropTypes.func.isRequired,
submit: PropTypes.func.isRequired,
loading: PropTypes.bool
};
changeField (field, value) {
this.props.changeField(field, value);
}
render () {
const {username, password, name, email, submit, loading} = this.props;
return (
);
}
}
================================================
FILE: lib/shared/screens/admin/screens/users/components/users.jsx
================================================
import Component from 'components/component';
import Content from 'components/content';
import ContentDisplays from 'components/content-displays';
import ContentHeader from 'components/content-header';
import ContentHeaderActions from 'components/content-header-actions';
import ContentNew from 'components/content-new';
import ContentSearch from 'components/content-search';
import Modal from 'components/modal';
import ModalDelete from 'components/modal-delete';
import React, {PropTypes} from 'react';
import List from './list';
import New from './new';
export default class Users extends Component {
static fragments = List.fragments;
static propTypes = {
users: PropTypes.array.isRequired,
openNew: PropTypes.func.isRequired,
newOpened: PropTypes.bool.isRequired,
closeNew: PropTypes.func.isRequired,
onDelete: PropTypes.func.isRequired,
deleteConfirm: PropTypes.bool,
deleteConfirmUser: PropTypes.object,
cancelDelete: PropTypes.func.isRequired,
confirmDelete: PropTypes.func.isRequired,
deletingUser: PropTypes.bool,
search: PropTypes.string.isRequired,
searchChange: PropTypes.func.isRequired
};
render () {
const {users, openNew, onDelete, search, searchChange} = this.props;
return (
Add new user
{this.renderNew()}
{this.renderDeleteConfirm()}
);
}
renderNew () {
const {newOpened, closeNew} = this.props;
if (newOpened) {
return (
);
}
}
renderDeleteConfirm () {
const {deleteConfirm, deleteConfirmUser, cancelDelete, confirmDelete, deletingUser} = this.props;
if (deleteConfirm) {
return (
);
}
}
}
================================================
FILE: lib/shared/screens/admin/screens/users/index.js
================================================
import bind from 'decorators/bind';
import Component from 'components/component';
import React, {PropTypes} from 'react';
import {removeUser} from 'actions/users';
import {bindActionCreators} from 'redux';
import {dataConnect} from 'relate-js';
import Users from './components/users.jsx';
@dataConnect(
() => ({}),
(dispatch) => bindActionCreators({removeUser}, dispatch),
() => ({
fragments: Users.fragments,
mutations: {
addUser: [{
type: 'APPEND',
field: 'users'
}]
}
})
)
export default class UsersContainer extends Component {
static propTypes = {
users: PropTypes.array.isRequired,
location: PropTypes.object.isRequired,
removeUser: PropTypes.func.isRequired
};
static defaultProps = {
users: []
};
getInitState () {
return {
newOpened: false,
search: ''
};
}
@bind
openNew () {
this.setState({
newOpened: true
});
}
@bind
closeNew () {
this.setState({
newOpened: false
});
}
@bind
searchChange (search) {
this.setState({
search
});
}
@bind
onDelete (user) {
this.setState({
deleteConfirm: true,
deleteConfirmUser: user
});
}
@bind
cancelDelete () {
this.setState({
deleteConfirm: false,
deleteConfirmUser: null,
deletingUser: false
});
}
@bind
confirmDelete () {
const {deleteConfirmUser} = this.state;
this.setState({
deletingUser: true
});
this.props.removeUser(deleteConfirmUser._id).then(() => {
this.cancelDelete();
});
}
render () {
const {users} = this.props;
return (
);
}
}
================================================
FILE: lib/shared/screens/admin/shared/components/balloon/index.jsx
================================================
import cx from 'classnames';
import Component from 'components/component';
import Portal from 'components/portal';
import Stick from 'components/stick';
import React, {PropTypes} from 'react';
import styles from './index.less';
export default class Balloon extends Component {
static propTypes = {
children: PropTypes.node.isRequired,
element: PropTypes.any.isRequired,
stickOptions: PropTypes.object,
white: PropTypes.bool,
small: PropTypes.bool,
unpadded: PropTypes.bool
};
render () {
const {stickOptions, white, small, unpadded} = this.props;
const stickProps = Object.assign({
verticalPosition: 'bottom',
horizontalPosition: 'left',
transition: 'slideUpIn',
horizontalOffset: -9
}, stickOptions);
return (
);
}
}
================================================
FILE: lib/shared/screens/admin/shared/components/balloon/index.less
================================================
.balloon {
position: relative;
width: 270px;
padding: 11px;
border-radius: 4px;
background-color: #ffffff;
z-index: 1;
-webkit-box-shadow: 0px 5px 25px 0px rgba(0,0,0,0.5);
-moz-box-shadow: 0px 5px 25px 0px rgba(0,0,0,0.5);
box-shadow: 0px 5px 25px 0px rgba(0,0,0,0.5);
transform: translateY(3px);
&.white {
-webkit-box-shadow: 0px 5px 20px 0px rgba(0,0,0,0.2);
-moz-box-shadow: 0px 5px 20px 0px rgba(0,0,0,0.2);
box-shadow: 0px 5px 20px 0px rgba(0,0,0,0.2);
}
&.small {
width: 200px;
}
&.unpadded {
padding: 11px 0;
}
}
.triangle {
position: absolute;
background-color: #ffffff;
text-align: left;
display: inline-block;
top: -9px;
left: 50%;
transform: translateX(-5px) rotate(-60deg) skewX(-30deg) scale(1,.866);
&.right {
left: auto;
right: 100px;
}
&:before, &:after {
content: '';
position: absolute;
background-color: inherit;
}
&, &:before, &:after {
width: 10px;
height: 10px;
border-top-right-radius: 40%;
}
&:before {
transform: rotate(-135deg) skewX(-45deg) scale(1.414,.707) translate(0,-50%);
}
&:after {
transform: rotate(135deg) skewY(-45deg) scale(.707,1.414) translate(50%);
}
}
================================================
FILE: lib/shared/screens/admin/shared/components/content/index.jsx
================================================
import Component from 'components/component';
import Scrollable from 'components/scrollable';
import React, {PropTypes} from 'react';
import styles from './index.less';
export default class Content extends Component {
static propTypes = {
children: PropTypes.node.isRequired
};
render () {
return (
{this.props.children}
);
}
}
================================================
FILE: lib/shared/screens/admin/shared/components/content/index.less
================================================
.root {
position: absolute;
top: 70px;
left: 0;
right: 0;
bottom: 0;
}
.content {
padding: 50px;
}
================================================
FILE: lib/shared/screens/admin/shared/components/content-displays/index.jsx
================================================
import cx from 'classnames';
import Component from 'components/component';
import React, {PropTypes} from 'react';
import styles from './index.less';
export default class ContentDisplays extends Component {
static propTypes = {
display: PropTypes.oneOf(['grid', 'list']).isRequired,
onChange: PropTypes.func.isRequired
};
init () {
this.onGridClick = this.onClick.bind(this, 'grid');
this.onListClick = this.onClick.bind(this, 'list');
}
onClick (to) {
this.props.onChange(to);
}
render () {
const {display} = this.props;
return (
);
}
}
================================================
FILE: lib/shared/screens/admin/shared/components/content-displays/index.less
================================================
@import '~styles/colors.less';
.root {
display: inline-block;
vertical-align: top;
padding: 0 15px;
}
.button {
display: inline-block;
vertical-align: top;
width: 30px;
height: 30px;
margin: 0 5px;
:global i {
font-size: 22px;
line-height: 30px;
color: @adminText;
}
}
.active {
:global i {
color: @primary;
}
}
================================================
FILE: lib/shared/screens/admin/shared/components/content-header/index.jsx
================================================
import cx from 'classnames';
import Component from 'components/component';
import React, {PropTypes} from 'react';
import styles from './index.less';
export default class ContentHeader extends Component {
static propTypes = {
children: PropTypes.node.isRequired,
smallPadding: PropTypes.bool
};
static defaultProps = {
smallPadding: false
};
render () {
const {smallPadding} = this.props;
return (
{this.props.children}
);
}
}
================================================
FILE: lib/shared/screens/admin/shared/components/content-header/index.less
================================================
@import '~styles/colors.less';
.root {
height: 70px;
border-bottom: 1px solid @adminBorders;
padding: 20px 20px;
line-height: 30px;
}
.smallPadding {
padding: 10px 20px;
}
================================================
FILE: lib/shared/screens/admin/shared/components/content-header-actions/index.jsx
================================================
import Component from 'components/component';
import React, {PropTypes} from 'react';
import styles from './index.less';
export default class ContentHeaderActions extends Component {
static propTypes = {
children: PropTypes.node.isRequired
};
render () {
return (
{this.props.children}
);
}
}
================================================
FILE: lib/shared/screens/admin/shared/components/content-header-actions/index.less
================================================
.root {
float: right;
}
================================================
FILE: lib/shared/screens/admin/shared/components/content-loading/index.jsx
================================================
import Animate from 'components/animate';
import Component from 'components/component';
import React from 'react';
import Spinner from 'components/spinner';
import styles from './index.less';
export default class ContentLoading extends Component {
render () {
return (
);
}
}
================================================
FILE: lib/shared/screens/admin/shared/components/content-loading/index.less
================================================
.root {
position: absolute;
top: 0; left: 0; right: 0; bottom: 0;
}
.center {
display: inline-block;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
================================================
FILE: lib/shared/screens/admin/shared/components/content-new/index.jsx
================================================
import Component from 'components/component';
import React, {PropTypes} from 'react';
import styles from './index.less';
export default class ContentNew extends Component {
static propTypes = {
children: PropTypes.node.isRequired,
onClick: PropTypes.func.isRequired
};
render () {
const {children, onClick} = this.props;
return (
);
}
}
================================================
FILE: lib/shared/screens/admin/shared/components/content-new/index.less
================================================
@import '~styles/colors.less';
.button {
display: inline-block;
vertical-align: top;
padding: 0 20px;
color: @adminText;
text-transform: uppercase;
font-size: 12px;
}
================================================
FILE: lib/shared/screens/admin/shared/components/content-search/index.jsx
================================================
import Component from 'components/component';
import React, {PropTypes} from 'react';
import styles from './index.less';
export default class ContentSearch extends Component {
static propTypes = {
value: PropTypes.string.isRequired,
onChange: PropTypes.func.isRequired
};
onChange (event) {
this.props.onChange(event.target.value);
}
render () {
const {value} = this.props;
return (
);
}
}
================================================
FILE: lib/shared/screens/admin/shared/components/content-search/index.less
================================================
@import '~styles/colors.less';
.root {
display: inline-block;
vertical-align: top;
:global i {
font-size: 24px;
line-height: 30px;
color: @adminTextSub;
margin-right: 10px;
}
}
.input {
display: inline-block;
vertical-align: top;
color: @adminTextSub;
font-size: 20px;
line-height: 30px;
font-weight: 300;
background-color: transparent;
border: 0;
outline: 0;
padding: 0;
margin: 0;
width: 200px;
&::-webkit-input-placeholder {
color: @adminTextSub;
}
&:-moz-placeholder {
color: @adminTextSub;
}
&::-moz-placeholder {
color: @adminTextSub;
}
&:-ms-input-placeholder {
color: @adminTextSub;
}
&::-ms-input-placeholder {
color: @adminTextSub;
}
&:placeholder-shown {
color: @adminTextSub;
}
}
================================================
FILE: lib/shared/screens/admin/shared/components/content-sidebar/index.jsx
================================================
import velocity from 'velocity-animate';
import Component from 'components/component';
import React, {PropTypes} from 'react';
import styles from './index.less';
export default class ContentSidebar extends Component {
static propTypes = {
children: PropTypes.node.isRequired,
opened: PropTypes.bool.isRequired
};
componentWillReceiveProps (nextProps) {
if (this.props.opened !== nextProps.opened) {
const config = {
duration: 800,
display: null,
easing: 'easeOutExpo'
};
if (nextProps.opened) {
velocity(this.refs.root, {right: '0px'}, config);
} else {
velocity(this.refs.root, {right: '-290px'}, config);
}
}
}
render () {
return (
{this.props.children}
);
}
}
================================================
FILE: lib/shared/screens/admin/shared/components/content-sidebar/index.less
================================================
@import '~styles/colors.less';
@import '~styles/sizes.less';
.root {
position: absolute;
border-top: 1px solid @adminBorders;
border-left: 1px solid @adminBorders;
top: -1px; right: -@menuWidth; bottom: 0;
width: @menuWidth;
background-color: #ffffff;
z-index: 11;
}
================================================
FILE: lib/shared/screens/admin/shared/components/editable-title/index.jsx
================================================
import cx from 'classnames';
import Component from 'components/component';
import React, {PropTypes} from 'react';
import styles from './index.less';
export default class EditableTitle extends Component {
static propTypes = {
value: PropTypes.string.isRequired,
sub: PropTypes.bool,
onSubmit: PropTypes.func.isRequired
};
getInitState () {
return {
editing: false
};
}
componentWillReceiveProps (nextProps) {
if (this.state.editing && nextProps.value !== this.props.value) {
this.setState({
editing: false
});
}
}
componentDidUpdate (prevProps, prevState) {
if (!prevState.editing && this.state.editing) {
const input = this.refs.input;
if (input) {
input.focus && input.focus();
const len = this.state.editValue.length;
input.setSelectionRange && input.setSelectionRange(len, len);
}
}
}
onClick () {
this.setState({
editing: true,
editValue: this.props.value
});
}
onChange (event) {
this.setState({
editValue: event.target.value
});
}
cancel (event) {
event.preventDefault();
this.setState({
editing: false,
editValue: ''
});
}
onSubmit (event) {
event.preventDefault();
this.props
.onSubmit(this.state.editValue)
.then(() => {
this.setState({
editing: false
});
});
}
render () {
const {sub} = this.props;
return (
{this.renderContent()}
);
}
renderContent () {
const {editing} = this.state;
const {value} = this.props;
let result;
if (!editing) {
result = (
);
} else {
result = (
);
}
return result;
}
}
================================================
FILE: lib/shared/screens/admin/shared/components/editable-title/index.less
================================================
@import '~styles/colors.less';
.title {
display: inline-block;
font-weight: 300;
font-size: 18px;
line-height: 26px;
height: 26px;
color: @adminTextHighlight;
}
.editButton {
:global i {
color: @adminText;
font-size: 16px;
line-height: 26px;
margin-left: 10px;
opacity: 0;
transition: all 0.1s ease-out;
}
&:hover :global i {
opacity: 1;
}
}
.input {
display: inline-block;
vertical-align: top;
border: 0;
outline: 0;
padding: 0;
width: 350px;
margin-right: 10px;
}
.formButton {
display: inline-block;
vertical-align: top;
text-transform: uppercase;
font-size: 11px;
margin-left: 10px;
line-height: 26px;
height: 26px;
}
.confirmButton {
color: @primary;
}
.cancelButton {
color: @adminText;
&:hover {
color: @alert;
}
}
.sub {
.title {
font-size: 12px;
line-height: 25px;
height: 25px;
color: @adminText;
}
.editButton {
:global i {
font-size: 13px;
line-height: 25px;
}
}
.formButton {
line-height: 25px;
height: 25px;
}
}
================================================
FILE: lib/shared/screens/admin/shared/components/input-options/border/index.jsx
================================================
import cx from 'classnames';
import BorderStyle from 'components/input-options/border-style';
import ColorPicker from 'components/input-options/color';
import Component from 'components/component';
import NumberInput from 'components/input-options/number';
import React from 'react';
import styles from './index.less';
export default class BorderPicker extends Component {
static propTypes = {
value: React.PropTypes.object.isRequired,
onChange: React.PropTypes.func.isRequired
};
getInitState () {
this.onWidhtChange = this.onInputChange.bind(this, 'width');
this.onColorChange = this.onInputChange.bind(this, 'color');
this.onStyleChange = this.onInputChange.bind(this, 'style');
return {
selected: 'center',
values: this.parseValue(this.props.value)
};
}
componentWillReceiveProps (nextProps) {
this.setState({
values: this.parseValue(nextProps.value)
});
}
onInputChange (id, value) {
if (this.state.selected === 'center') {
this.state.values.top[id] = value;
this.state.values.right = Object.assign({}, this.state.values.top);
this.state.values.bottom = Object.assign({}, this.state.values.top);
this.state.values.left = Object.assign({}, this.state.values.top);
this.state.values.equal = true;
} else {
this.state.values[this.state.selected][id] = value;
}
this.props.onChange(Object.assign({}, this.state.values));
}
parseValue (value) {
const result = {
top: {
style: 'solid',
width: 1,
color: {
value: '#000000',
opacity: 100
}
},
left: {
style: 'solid',
width: 1,
color: {
value: '#000000',
opacity: 100
}
},
right: {
style: 'solid',
width: 1,
color: {
value: '#000000',
opacity: 100
}
},
bottom: {
style: 'solid',
width: 1,
color: {
value: '#000000',
opacity: 100
}
},
equal: false
};
if (value) {
result.top = value.top || result.top;
result.left = value.left || result.left;
result.right = value.right || result.right;
result.bottom = value.bottom || result.bottom;
}
if (this.equal(result.top, result.right) &&
this.equal(result.top, result.bottom) &&
this.equal(result.top, result.left)) {
result.equal = true;
} else {
result.equal = false;
}
return result;
}
equal (comp1, comp2) {
return (
comp1.style === comp2.style &&
comp1.width === comp2.width &&
comp1.color.value === comp2.color.value &&
comp1.color.opacity === comp2.color.opacity
);
}
changeSelected (selected, event) {
event.preventDefault();
this.setState({
selected
});
}
render () {
const values = this.state.values;
let value = 0;
let inactive = false;
if (this.state.selected !== 'center') {
value = values[this.state.selected];
} else {
inactive = !values.equal;
value = values.top;
if (inactive) {
value.style = 'solid';
}
}
return (
{this.renderToggleButton('top', !values.equal)}
{this.renderToggleButton('left', !values.equal)}
{this.renderToggleButton('right', !values.equal)}
{this.renderToggleButton('bottom', !values.equal)}
{this.renderToggleButton('center', values.equal)}
);
}
renderToggleButton (pos, active) {
const changeSelected = this.changeSelected.bind(this, pos);
return (
);
}
}
================================================
FILE: lib/shared/screens/admin/shared/components/input-options/border/index.less
================================================
@import '~styles/colors.less';
@total: 42px;
@btnSize: 20px;
@nonActive: #979797;
.toggles {
display: inline-block;
vertical-align: top;
}
.toggles {
position: relative;
height: @total;
width: @total;
}
.option {
display: inline-block;
vertical-align: right;
margin-left: 10px;
}
.colorPicker {
width: 110px;
}
.borderStyle {
margin-top: 15px;
text-align: right;
}
// Toggle buttons
.toggle {
position: absolute;
text-align: center;
display: inline-block;
cursor: pointer;
width: @btnSize;
height: @btnSize;
@center: 11px;
&.top {
left: @center;
top: 0;
border-top: 2px solid @nonActive;
}
&.left {
left: 0;
top: @center;
border-left: 2px solid @nonActive;
}
&.center {
left: @center + 3px;
top: @center + 3px;
width: 14px;
height: 14px;
border: 2px solid @nonActive;
}
&.right {
right: 0;
top: @center;
border-right: 2px solid @nonActive;
}
&.bottom {
left: @center;
bottom: 0;
border-bottom: 2px solid @nonActive;
}
&.selected {
border-color: @primary;
}
&.active {
border-color: #ffffff;
}
&.selected.active {
border-color: @primary;
}
}
================================================
FILE: lib/shared/screens/admin/shared/components/input-options/border-style/index.jsx
================================================
import cx from 'classnames';
import Component from 'components/component';
import React, {PropTypes} from 'react';
import styles from './index.less';
export default class BorderStyle extends Component {
static propTypes = {
value: PropTypes.string.isRequired,
onChange: PropTypes.func.isRequired,
className: PropTypes.string
};
onClick (type, event) {
event.preventDefault();
this.props.onChange(type);
}
render () {
return (
{this.renderOption('solid')}
{this.renderOption('dashed')}
{this.renderOption('dotted')}
{this.renderOption('double')}
);
}
renderOption (type) {
const {value} = this.props;
const onClick = this.onClick.bind(this, type);
return (
);
}
}
================================================
FILE: lib/shared/screens/admin/shared/components/input-options/border-style/index.less
================================================
@import '~styles/colors.less';
@btnsSize: 16px;
@btnsSpacing: 14px;
@nonActive: #979797;
@active: #ffffff;
.option {
position: relative;
display: inline-block;
vertical-align: top;
width: @btnsSize;
height: @btnsSize;
margin-right: @btnsSpacing;
border: 1px solid @chromeBordersColor;
cursor: pointer;
text-align: center;
&:last-child {
margin-right: 0;
}
&:hover {
border-color: @active;
}
}
.dashed {
border-style: dashed;
}
.dotted {
border-style: dotted;
}
.double {
border-width: 3px;
border-style: double;
}
.active {
border-color: @active;
}
================================================
FILE: lib/shared/screens/admin/shared/components/input-options/box-shadow/edit.jsx
================================================
import Component from 'components/component';
import React, {PropTypes} from 'react';
export default class Edit extends Component {
static propTypes = {
shadow: PropTypes.object.isRequired,
changeShadow: PropTypes.func.isRequired,
OptionsList: PropTypes.object.isRequired
};
static options = [
{
type: 'Columns',
options: [
{
label: 'Color',
type: 'Color',
id: 'color'
},
{
label: 'Blur',
type: 'Pixels',
id: 'blur'
}
]
},
{
type: 'Columns',
options: [
{
label: 'X',
type: 'Pixels',
id: 'x'
},
{
label: 'Y',
type: 'Pixels',
id: 'y'
}
]
},
{
type: 'Columns',
options: [
{
label: 'Spread',
type: 'Pixels',
id: 'spread'
},
{
label: 'Inset/Outset',
type: 'ShadowPosition',
id: 'type'
}
]
}
];
render () {
const {shadow, OptionsList, changeShadow} = this.props;
return (
);
}
}
================================================
FILE: lib/shared/screens/admin/shared/components/input-options/box-shadow/index.jsx
================================================
import cloneDeep from 'lodash.clonedeep';
import Component from 'components/component';
import React, {PropTypes} from 'react';
import styles from './index.less';
import Shadow from './shadow';
export default class BoxShadow extends Component {
static propTypes = {
value: PropTypes.array.isRequired,
onChange: PropTypes.func.isRequired
};
static defaultProps = {
value: []
};
getInitState () {
return {
editingShadow: false
};
}
addNewClick () {
this.props.onChange([...this.props.value, {
type: 'outset',
color: '#000000',
spread: '2px',
blur: '2px',
x: '2px',
y: '2px'
}]);
this.setState({
editingShadow: this.props.value.length
});
}
changeShadow (key, value) {
if (this.state.editingShadow !== false) {
const newValue = cloneDeep(this.props.value);
newValue[this.state.editingShadow][key] = value;
this.props.onChange(newValue);
}
}
selectShadow (index) {
if (this.state.editingShadow === index) {
this.setState({
editingShadow: false
});
} else {
this.setState({
editingShadow: index
});
}
}
removeShadow (index) {
const newValue = cloneDeep(this.props.value);
newValue.splice(index, 1);
this.props.onChange(newValue);
}
render () {
return (
{this.props.value.map(this.renderEntry, this)}
Add new shadow
);
}
renderEntry (shadow, index) {
return (
);
}
}
================================================
FILE: lib/shared/screens/admin/shared/components/input-options/box-shadow/index.less
================================================
@import '~styles/colors.less';
.addButton {
font-size: 10px;
color: #dbdbdb;
cursor: pointer;
line-height: 20px;
text-align: center;
text-transform: uppercase;
margin-top: 15px;
&:hover {
color: @primary;
}
}
================================================
FILE: lib/shared/screens/admin/shared/components/input-options/box-shadow/shadow.jsx
================================================
import Balloon from 'components/balloon';
import Component from 'components/component';
import React, {PropTypes} from 'react';
import {getColor} from 'helpers/colors';
import styles from './shadow.less';
import Edit from './edit';
export default class Shadow extends Component {
static propTypes = {
shadow: PropTypes.object.isRequired,
editing: PropTypes.bool.isRequired,
new: PropTypes.bool.isRequired,
selectShadow: PropTypes.func.isRequired,
removeShadow: PropTypes.func.isRequired,
index: PropTypes.number.isRequired
};
onClick () {
this.props.selectShadow(this.props.index);
}
onRemove (event) {
event.preventDefault();
event.stopPropagation();
this.props.removeShadow(this.props.index);
}
render () {
const {shadow} = this.props;
const colorLabel = getColor(shadow.color).label;
return (
{
this.ref = ref;
!this.state.ready && this.setState({ready: true});
}}
>
{`${shadow.type}, ${colorLabel}, ${shadow.x} ${shadow.y}, ${shadow.blur}, ${shadow.spread}`}
{this.renderEditing()}
);
}
renderEditing () {
if (this.props.editing && this.state.ready) {
return (
);
}
}
}
================================================
FILE: lib/shared/screens/admin/shared/components/input-options/box-shadow/shadow.less
================================================
@import '~styles/colors.less';
.root {
position: relative;
}
.content {
position: relative;
border: 1px solid @chromeBordersColor;
padding: 12px;
font-size: 10px;
line-height: 14px;
color: @chromeTextColor;
border-radius: 4px;
cursor: pointer;
margin-bottom: 8px;
&:hover {
border-color: @primary;
.removeButton {
visibility: visible;
}
}
}
.removeButton {
position: absolute;
right: 1px; top: 1px; bottom: 1px;
width: 30px;
text-align: center;
visibility: hidden;
background: -moz-linear-gradient(left, rgba(51,54,59,0) 0%, rgba(51,54,59,1) 23%, rgba(51,54,59,1) 100%);
background: -webkit-linear-gradient(left, rgba(51,54,59,0) 0%,rgba(51,54,59,1) 23%,rgba(51,54,59,1) 100%);
background: linear-gradient(to right, rgba(51,54,59,0) 0%,rgba(51,54,59,1) 23%,rgba(51,54,59,1) 100%);
:global i {
color: @chromeTextSubColor;
line-height: 36px;
font-size: 11px;
}
&:hover {
:global i {
color: #ffffff;
}
}
}
================================================
FILE: lib/shared/screens/admin/shared/components/input-options/button/index.js
================================================
import * as pageBuilderActionsArr from 'actions/page-builder';
import Button from 'components/button';
import Component from 'components/component';
import React, {PropTypes} from 'react';
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
@connect(
(state) => ({
selectedId: state.pageBuilder.selectedId
}),
(dispatch) => ({
pageBuilderActions: bindActionCreators(pageBuilderActionsArr, dispatch)
})
)
export default class ButtonContainer extends Component {
static propTypes = {
label: PropTypes.node.isRequired,
action: PropTypes.string.isRequired,
actionProps: PropTypes.object.isRequired,
pageBuilderActions: PropTypes.object.isRequired,
selectedId: PropTypes.string
};
onClick (event) {
event.preventDefault();
const {action, actionProps, pageBuilderActions, selectedId} = this.props;
if (action === 'addElement') {
pageBuilderActions.addElementAt(actionProps, {
id: selectedId,
position: 0
});
} else if (action === 'linkData') {
pageBuilderActions.linkDataMode(selectedId);
} else if (action === 'linkFormData') {
pageBuilderActions.linkFormDataMode(selectedId);
}
}
render () {
return (
);
}
}
================================================
FILE: lib/shared/screens/admin/shared/components/input-options/checkbox/index.jsx
================================================
import bind from 'decorators/bind';
import cx from 'classnames';
import Component from 'components/component';
import React from 'react';
import styles from './index.less';
export default class Checkbox extends Component {
static propTypes = {
value: React.PropTypes.bool.isRequired,
onChange: React.PropTypes.func.isRequired,
disabled: React.PropTypes.bool,
white: React.PropTypes.bool
};
@bind
toggle (event) {
event.preventDefault();
const {disabled, onChange, value} = this.props;
if (!disabled && onChange) {
onChange(!value);
}
}
render () {
const {disabled, value, white} = this.props;
return (
{value && }
);
}
}
================================================
FILE: lib/shared/screens/admin/shared/components/input-options/checkbox/index.less
================================================
@import '~styles/colors.less';
@size: 15px;
.checkbox {
display: inline-block;
width: @size;
height: @size;
text-align: center;
border: 1px solid @chromeTextSubColor;
border-radius: 3px;
cursor: pointer;
:global i {
font-size: 9px;
color: #ffffff;
line-height: @size - 2px;
}
&:hover {
border-color: @primary;
}
}
.white.checkbox {
:global i {
color: @primary;
}
}
================================================
FILE: lib/shared/screens/admin/shared/components/input-options/color/color-palette-picker.jsx
================================================
import cx from 'classnames';
import Component from 'components/component';
import Portal from 'components/portal';
import Stick from 'components/stick';
import React, {PropTypes} from 'react';
import {applyBackground} from 'helpers/colors';
import styles from './color-palette-picker.less';
import Edit from './edit';
export default class ColorPicker extends Component {
static propTypes = {
value: PropTypes.object,
onChange: PropTypes.func,
toggleOpened: PropTypes.func.isRequired,
className: PropTypes.string,
colr: PropTypes.object.isRequired,
opacity: PropTypes.number.isRequired,
colors: PropTypes.array.isRequired,
opened: PropTypes.bool.isRequired,
label: PropTypes.string.isRequired,
type: PropTypes.string.isRequired,
white: PropTypes.bool
};
toggleOpen (event) {
event.preventDefault();
event.stopPropagation();
this.props.toggleOpened();
}
render () {
const {colors, value, type, white} = this.props;
const colorStyle = {};
applyBackground(colorStyle, value, colors);
let label = this.props.label;
if (type === 'linear') {
label = 'Linear Grad.';
} else if (type === 'radial') {
label = 'Radial Grad.';
}
return (
{this.ref = ref;}}>
{label}
{this.renderContent()}
);
}
renderContent () {
if (this.props.opened) {
return (
);
}
}
}
================================================
FILE: lib/shared/screens/admin/shared/components/input-options/color/color-palette-picker.less
================================================
@import '~styles/colors.less';
.info {
max-width: 130px;
height: 38px;
border: 1px solid @chromeBordersColor;
border-radius: 3px;
overflow: hidden;
background-color: @chromeBackgroundDarkerColor;
cursor: pointer;
&:hover {
border-color: @primary;
}
}
.preview {
position: relative;
display: inline-block;
vertical-align: top;
width: 36px;
height: 36px;
&:before {
content: '';
position: absolute;
top: 0; right: 0; bottom: 0; left: 0;
background: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAIAAABLbSncAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAChJREFUeNpiPHPmDAMMGBsbw9lMDDgA6RKM%2F%2F%2F%2Fh3POnj1LCzsAAgwAQtYIcFfEyzkAAAAASUVORK5CYII%3D');
}
}
.color {
position: relative;
display: block;
width: 100%;
height: 100%;
}
.label {
display: inline-block;
vertical-align: top;
line-height: 36px;
padding: 0 14px;
font-size: 10px;
color: @chromeTextColor;
}
.white {
.info {
background-color: transparent;
border-color: @adminInputBorders;
&:hover {
border-color: @primary;
}
}
.label {
color: @adminText;
}
}
================================================
FILE: lib/shared/screens/admin/shared/components/input-options/color/color-picker.jsx
================================================
import Component from 'components/component';
import React, {PropTypes} from 'react';
import styles from './color-picker.less';
import Hue from './hue';
import SatLight from './sat-light';
export default class ColorPicker extends Component {
static propTypes = {
colr: PropTypes.object.isRequired
};
render () {
const {colr} = this.props;
const hsv = colr.toHsvObject();
return (
);
}
}
================================================
FILE: lib/shared/screens/admin/shared/components/input-options/color/color-picker.less
================================================
.root {
position: relative;
width: 100%;
height: 184px;
}
================================================
FILE: lib/shared/screens/admin/shared/components/input-options/color/color.jsx
================================================
import Component from 'components/component';
import Portal from 'components/portal';
import Stick from 'components/stick';
import React, {PropTypes} from 'react';
import {findDOMNode} from 'react-dom';
import styles from './color.less';
export default class Color extends Component {
static fragments = {
color: {
_id: 1,
label: 1,
value: 1
}
};
static propTypes = {
color: PropTypes.object.isRequired,
selectColor: PropTypes.func.isRequired,
addOverlay: PropTypes.func.isRequired,
closeOverlay: PropTypes.func.isRequired
};
getInitState () {
return {
overed: false
};
}
onClick () {
this.props.selectColor(this.props.color._id);
}
onMouseEnter () {
this.setState({
overed: true,
element: findDOMNode(this)
});
}
onMouseLeave () {
this.setState({
overed: false
});
}
render () {
const style = {
backgroundColor: this.props.color.value
};
return (
{this.renderInfo()}
);
}
renderInfo () {
const {color} = this.props;
if (this.state.overed) {
return (
{color.label}
);
}
}
}
================================================
FILE: lib/shared/screens/admin/shared/components/input-options/color/color.less
================================================
.color {
width: 20px;
height: 20px;
display: inline-block;
vertical-align: top;
border-radius: 4px;
margin-right: 6px;
margin-top: 6px;
cursor: pointer;
border: 1px solid rgba(0, 0, 0, 0.2);
}
.colorTitleBallon {
pointer-events: none;
}
.colorTitle {
background-color: #ffffff;
min-width: 70px;
height: 22px;
border: 1px solid #dbdbdb;
text-align: center;
line-height: 20px;
border-radius: 4px;
font-size: 10px;
color: #999999;
pointer-events: none;
}
.label {
position: relative;
}
.triangle {
position: absolute;
background-color: #ffffff;
border: 1px solid #dbdbdb;
text-align: left;
display: inline-block;
top: 17px;
left: 50%;
transform: translateX(-4px) rotate(120deg) skewX(-30deg) scale(1,.866);
&:before, &:after {
content: '';
position: absolute;
background-color: inherit;
}
&, &:before, &:after {
width: 8px;
height: 8px;
border-top-right-radius: 40%;
}
&:before {
transform: rotate(-135deg) skewX(-45deg) scale(1.414,.707) translate(0,-50%);
}
&:after {
transform: rotate(135deg) skewY(-45deg) scale(.707,1.414) translate(50%);
}
}
================================================
FILE: lib/shared/screens/admin/shared/components/input-options/color/colors-collection.jsx
================================================
import Component from 'components/component';
import Input from 'components/input-options/input';
import React, {PropTypes} from 'react';
import styles from './colors-collection.less';
import Color from './color';
export default class ColorsCollection extends Component {
static propTypes = {
colors: PropTypes.array.isRequired,
selectColor: PropTypes.func.isRequired,
addOverlay: PropTypes.func.isRequired,
closeOverlay: PropTypes.func.isRequired,
addingColor: PropTypes.bool.isRequired,
addingColorName: PropTypes.string.isRequired,
changeAddingColor: PropTypes.func.isRequired,
toggleAddingColor: PropTypes.func.isRequired,
addColor: PropTypes.func.isRequired
};
onSubmit (event) {
event.preventDefault();
this.props.addColor();
}
render () {
return (
Color Collection
{this.props.colors.map(this.renderColor, this)}
{this.renderAdding()}
);
}
renderColor (color) {
return (
);
}
renderAdding () {
if (this.props.addingColor) {
return (
);
}
}
}
================================================
FILE: lib/shared/screens/admin/shared/components/input-options/color/colors-collection.less
================================================
@import '~styles/colors.less';
.root {
border-top: 2px solid #dbdbdb;
padding-top: 12px;
}
.label {
font-size: 10px;
text-transform: uppercase;
color: #999999;
line-height: 13px;
margin-bottom: 4px;
}
.addButton {
width: 20px;
height: 20px;
display: inline-block;
vertical-align: top;
margin-right: 6px;
margin-top: 6px;
cursor: pointer;
:global i {
font-size: 18px;
line-height: 20px;
text-align: center;
color: #999999;
}
&:hover :global i {
color: @primary;
}
}
.adding {
margin: 0;
display: block;
margin-top: 6px;
}
.input {
display: inline-block;
width: 75%;
:global input {
line-height: 28px;
}
}
.saveButton {
display: inline-block;
vertical-align: top;
width: 25%;
line-height: 30px;
color: @primary;
font-size: 12px;
text-transform: uppercase;
font-weight: 800;
text-align: center;
cursor: pointer;
&:hover {
color: #333333;
}
}
================================================
FILE: lib/shared/screens/admin/shared/components/input-options/color/edit.jsx
================================================
import cx from 'classnames';
import Component from 'components/component';
import React, {PropTypes} from 'react';
import {findDOMNode} from 'react-dom';
import styles from './edit.less';
import ColorPicker from './color-picker';
import ColorsCollection from './colors-collection';
import GradientPoints from './gradient-points';
import Inputs from './inputs';
import LinearGradient from './linear-gradient';
import Opacity from './opacity';
import RadialGradient from './radial-gradient';
import RadialRadius from './radial-radius';
import Types from './types';
export default class Edit extends Component {
static propTypes = {
type: PropTypes.string.isRequired,
colr: PropTypes.object.isRequired,
opacity: PropTypes.number.isRequired,
editingPoint: PropTypes.number.isRequired,
hsvChange: PropTypes.func.isRequired,
rgbChange: PropTypes.func.isRequired,
hexChange: PropTypes.func.isRequired,
opacityChange: PropTypes.func.isRequired,
inputType: PropTypes.string.isRequired,
previousInputType: PropTypes.func.isRequired,
nextInputType: PropTypes.func.isRequired,
gradients: PropTypes.bool.isRequired,
side: PropTypes.string.isRequired,
changeToSolid: PropTypes.func.isRequired,
changeToLinear: PropTypes.func.isRequired,
changeToRadial: PropTypes.func.isRequired,
value: PropTypes.object.isRequired,
colors: PropTypes.array.isRequired,
changeEditingPoint: PropTypes.func.isRequired,
pointPercChange: PropTypes.func.isRequired,
toggleOpened: PropTypes.func.isRequired,
infoElement: PropTypes.any,
changeAngle: PropTypes.func.isRequired,
changeRadius: PropTypes.func.isRequired,
changeCenter: PropTypes.func.isRequired,
addPoint: PropTypes.func.isRequired,
removePoint: PropTypes.func.isRequired
};
componentDidMount () {
this.onCloseBind = ::this.onClose;
document.body.addEventListener('mousedown', this.onCloseBind, false);
}
componentWillUnmount () {
document.body.removeEventListener('mousedown', this.onCloseBind, false);
}
onClose (event) {
const holderRect = findDOMNode(this.refs.holder).getBoundingClientRect();
const outOfHolder =
(event.pageX < holderRect.left || event.pageX > holderRect.left + holderRect.width) ||
(event.pageY < holderRect.top || event.pageY > holderRect.top + holderRect.height);
let outOfGradient = true;
if (this.refs.linearGradient) {
const gradientRect = findDOMNode(this.refs.linearGradient).getBoundingClientRect();
outOfGradient =
(event.pageX < gradientRect.left - 10 || event.pageX > gradientRect.left + gradientRect.width + 10) ||
(event.pageY < gradientRect.top - 10 || event.pageY > gradientRect.top + gradientRect.height + 10);
}
let outOfRadial = true;
if (this.refs.radialGradient) {
const gradientRect = findDOMNode(this.refs.radialGradient).getBoundingClientRect();
outOfRadial =
(event.pageX < gradientRect.left - 10 || event.pageX > gradientRect.left + gradientRect.width + 10) ||
(event.pageY < gradientRect.top - 10 || event.pageY > gradientRect.top + gradientRect.height + 10);
}
let outOfInfo = true;
if (this.props.infoElement) {
const infoRect = findDOMNode(this.props.infoElement).getBoundingClientRect();
outOfInfo =
(event.pageX < infoRect.left || event.pageX > infoRect.left + infoRect.width) ||
(event.pageY < infoRect.top || event.pageY > infoRect.top + infoRect.height);
}
if (outOfHolder && outOfGradient && outOfInfo && outOfRadial) {
event.preventDefault();
event.stopPropagation();
this.props.toggleOpened();
}
}
render () {
const {
type,
colr,
opacity,
gradients,
hsvChange,
rgbChange,
hexChange,
opacityChange,
inputType,
previousInputType,
nextInputType,
editingPoint
} = this.props;
const isGradient = (type === 'linear' || type === 'radial');
return (
{gradients &&
}
{isGradient &&
}
{type === 'radial' &&
}
{type === 'linear' &&
}
{type === 'radial' &&
}
);
}
}
================================================
FILE: lib/shared/screens/admin/shared/components/input-options/color/edit.less
================================================
.root {
position: relative;
width: 270px;
padding: 6px;
border-radius: 4px;
background-color: #ffffff;
z-index: 1;
-webkit-box-shadow: 0px 5px 25px 0px rgba(0,0,0,0.5);
-moz-box-shadow: 0px 5px 25px 0px rgba(0,0,0,0.5);
box-shadow: 0px 5px 25px 0px rgba(0,0,0,0.5);
transform: translateY(3px);
}
.triangle {
position: absolute;
background-color: #ffffff;
text-align: left;
display: inline-block;
top: -9px;
left: 24px;
transform: rotate(-60deg) skewX(-30deg) scale(1,.866);
&:before, &:after {
content: '';
position: absolute;
background-color: inherit;
}
&, &:before, &:after {
width: 10px;
height: 10px;
border-top-right-radius: 40%;
}
&:before {
transform: rotate(-135deg) skewX(-45deg) scale(1.414,.707) translate(0,-50%);
}
&:after {
transform: rotate(135deg) skewY(-45deg) scale(.707,1.414) translate(50%);
}
}
================================================
FILE: lib/shared/screens/admin/shared/components/input-options/color/gradient-points.jsx
================================================
import cx from 'classnames';
import utils from 'helpers/utils';
import Component from 'components/component';
import React, {PropTypes} from 'react';
import {applyBackground, getColorString} from 'helpers/colors';
import styles from './gradient-points.less';
export default class GradientPoints extends Component {
static propTypes = {
editingPoint: PropTypes.number.isRequired,
value: PropTypes.object.isRequired,
colors: PropTypes.array.isRequired,
changeEditingPoint: PropTypes.func.isRequired,
pointPercChange: PropTypes.func.isRequired,
addPoint: PropTypes.func.isRequired,
removePoint: PropTypes.func.isRequired
};
constructor (props, children) {
super(props, children);
this.onMouseUpListener = this.onMouseUp.bind(this);
this.onMouseMoveListener = this.onMouseMove.bind(this);
}
componentWillUnmount () {
document.removeEventListener('mouseup', this.onMouseUpListener);
document.removeEventListener('mousemove', this.onMouseMoveListener);
}
onMouseDown (index, event) {
event.preventDefault();
event.stopPropagation();
this.activePoint = index;
document.addEventListener('mouseup', this.onMouseUpListener);
document.addEventListener('mousemove', this.onMouseMoveListener);
}
onMouseMove (event) {
event.preventDefault();
const bounds = utils.getOffsetRect(this.refs.bar);
const perc = utils.limitNumber((event.pageX - bounds.left) / bounds.width, 0, 1);
if (this.props.value.points.length > 2) {
const verticalOffset = Math.abs(event.pageY - bounds.top);
if (verticalOffset > 50) {
this.activePointDelete = true;
} else {
this.activePointDelete = false;
}
}
this.props.pointPercChange(this.activePoint, utils.roundSnap(perc * 100, [0, 25, 50, 75, 100]));
}
onMouseUp (event) {
event.preventDefault();
event.stopPropagation();
this.componentWillUnmount();
if (this.activePointDelete) {
this.activePointDelete = false;
this.props.removePoint(this.activePoint);
}
}
markerClicked (number, event) {
event.preventDefault();
event.stopPropagation();
this.props.changeEditingPoint(number);
}
addPoint (event) {
const bounds = utils.getOffsetRect(this.refs.bar);
const perc = utils.limitNumber((event.pageX - bounds.left) / bounds.width, 0, 1);
this.props.addPoint(Math.round(perc * 100));
}
render () {
const gradStyle = {};
applyBackground(gradStyle, Object.assign({}, this.props.value, {angle: 0, type: 'linear'}), this.props.colors);
return (
{this.props.value.points.map(this.renderPoint, this)}
);
}
renderPoint (colorObj, index) {
const markerStyle = {
left: `${colorObj.perc}%`,
transform: `translate(${-colorObj.perc}%, -50%)`
};
const selected = this.props.editingPoint === index;
if (selected) {
markerStyle.backgroundColor = getColorString(colorObj, this.props.colors);
}
if (this.activePoint === index && this.activePointDelete) {
markerStyle.visibility = 'hidden';
}
const onClick = this.markerClicked.bind(this, index);
const onMouseDown = this.onMouseDown.bind(this, index);
return (
);
}
}
================================================
FILE: lib/shared/screens/admin/shared/components/input-options/color/gradient-points.less
================================================
@import '~styles/colors.less';
.root {
width: 100%;
height: 10px;
margin-top: 10px;
margin-bottom: 4px;
position: relative;
}
.points {
position: absolute;
top: 0;
left: 0;
bottom: 0;
right: 14px;
border-radius: 4px;
&:before {
content: '';
position: absolute;
top: 0; right: 0; bottom: 0; left: 0;
opacity: 0.5;
background: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAIAAABLbSncAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAChJREFUeNpiPHPmDAMMGBsbw9lMDDgA6RKM%2F%2F%2F%2Fh3POnj1LCzsAAgwAQtYIcFfEyzkAAAAASUVORK5CYII%3D');
}
&:after {
content: '';
position: absolute;
display: block;
border-radius: 4px;
top: 0px; right: 0px; bottom: 0px; left: 0px;
border: 1px solid rgba(0, 0, 0, 0.2);
pointer-events: none;
}
}
.gradient {
position: absolute;
top: 0;
left: 0;
bottom: 0;
right: 0;
border-radius: 4px;
background: -moz-linear-gradient(left, rgba(255,0,0,0) 0%, rgba(255,0,0,1) 100%);
background: -webkit-linear-gradient(left, rgba(255,0,0,0) 0%,rgba(255,0,0,1) 100%);
background: linear-gradient(to right, rgba(255,0,0,0) 0%,rgba(255,0,0,1) 100%);
cursor: copy;
}
.marker {
position: absolute;
display: inline-block;
width: 8px;
height: 12px;
border: 1px solid rgba(0, 0, 0, 0.4);
background-color: #ffffff;
border-radius: 2px;
z-index: 1;
transform: translate(-50%, -50%);
top: 50%;
left: 50%;
cursor: pointer;
&.selected {
width: 14px;
height: 14px;
border: 2px solid #ffffff;
-webkit-box-shadow: 0px 0px 0px 1px rgba(0,0,0,0.4);
-moz-box-shadow: 0px 0px 0px 1px rgba(0,0,0,0.4);
box-shadow: 0px 0px 0px 1px rgba(0,0,0,0.4);
}
}
================================================
FILE: lib/shared/screens/admin/shared/components/input-options/color/hue.jsx
================================================
import utils from 'helpers/utils';
import Component from 'components/component';
import React, {PropTypes} from 'react';
import {findDOMNode} from 'react-dom';
import styles from './hue.less';
export default class Hue extends Component {
static propTypes = {
colr: PropTypes.object.isRequired,
hsv: PropTypes.object.isRequired,
hsvChange: PropTypes.func.isRequired
};
constructor (props, children) {
super(props, children);
this.onMouseUpListener = this.onMouseUp.bind(this);
this.onMouseMoveListener = this.onMouseMove.bind(this);
}
componentWillUnmount () {
document.removeEventListener('mouseup', this.onMouseUpListener);
document.removeEventListener('mousemove', this.onMouseMoveListener);
}
onMouseDown (event) {
event.preventDefault();
event.stopPropagation();
this.onMouseMove(event);
document.addEventListener('mouseup', this.onMouseUpListener);
document.addEventListener('mousemove', this.onMouseMoveListener);
}
onMouseMove (event) {
event.preventDefault();
const bounds = utils.getOffsetRect(findDOMNode(this));
const perc = utils.limitNumber((event.pageY - bounds.top) / bounds.height, 0, 1);
this.props.hsvChange({
h: perc * 360,
s: this.props.hsv.s,
v: this.props.hsv.v
});
}
onMouseUp (event) {
event.preventDefault();
event.stopPropagation();
this.componentWillUnmount();
}
render () {
const {hsv} = this.props;
const perc = (hsv.h / 360 * 100);
const markerStyle = {
top: `${perc}%`,
transform: `translate(-50%, ${-perc}%)`
};
return (
);
}
}
================================================
FILE: lib/shared/screens/admin/shared/components/input-options/color/hue.less
================================================
.root {
position: absolute;
top: 0;
bottom: 0;
right: 0;
width: 10px;
border-radius: 4px;
background: linear-gradient(to top, #FF0000 0%, #FF0099 10%, #CD00FF 20%, #3200FF 30%, #0066FF 40%, #00FFFD 50%, #00FF66 60%, #35FF00 70%, #CDFF00 80%, #FF9900 90%, #FF0000 100%);
&:after {
content: '';
position: absolute;
display: block;
border-radius: 4px;
top: 0px; right: 0px; bottom: 0px; left: 0px;
border: 1px solid rgba(0, 0, 0, 0.2);
}
}
.marker {
position: absolute;
display: inline-block;
width: 12px;
height: 8px;
border: 1px solid rgba(0, 0, 0, 0.4);
background-color: #ffffff;
border-radius: 2px;
z-index: 1;
transform: translate(-50%, -50%);
top: 50%;
left: 50%;
cursor: pointer;
}
================================================
FILE: lib/shared/screens/admin/shared/components/input-options/color/index.js
================================================
import * as colorsActions from 'actions/colors';
import forEach from 'lodash.foreach';
import sortBy from 'lodash.sortby';
import Component from 'components/component';
import React, {PropTypes} from 'react';
import {getColor} from 'helpers/colors';
import {bindActionCreators} from 'redux';
import {dataConnect} from 'relate-js';
import ColorPalettePicker from './color-palette-picker';
const INPUT_TYPES = ['hex', 'rgba', 'hsva'];
@dataConnect(
null,
(dispatch) => ({
colorsActions: bindActionCreators(colorsActions, dispatch)
}),
() => ({
fragments: {
colors: {
_id: 1,
label: 1,
value: 1
}
}
})
)
export default class ColorPalettePickerContainer extends Component {
static propTypes = {
value: PropTypes.object.isRequired,
onChange: PropTypes.func.isRequired,
colors: PropTypes.array,
colorsActions: PropTypes.object.isRequired,
gradients: PropTypes.bool.isRequired,
white: PropTypes.bool,
className: PropTypes.string
};
static defaultProps = {
colors: [],
gradients: false
};
getInitState () {
return {
opened: false,
inputType: 0,
addingColor: false,
addingColorName: '',
editingPoint: 0
};
}
onChange (changes) {
this.props.onChange(Object.assign({}, this.props.value || {}, changes));
}
selectColor (id) {
this.valueChange('palette', id);
}
hsvChange (hsv) {
this.valueChange('hsv', hsv);
}
hexChange (hex) {
this.valueChange('hex', hex);
}
rgbChange (rgb) {
this.valueChange('rgb', rgb);
}
valueChange (valueType, value) {
const type = this.props.value && this.props.value.type || 'hex';
if (type !== 'radial' && type !== 'linear') {
const currentColor = this.getCurrentColor();
this.props.onChange({
type: valueType,
value,
opacity: currentColor.opacity
});
} else {
const editingPoint = Math.min(this.state.editingPoint, this.props.value.points.length);
const points = [];
forEach(this.props.value.points, (point, index) => {
if (index === editingPoint) {
points.push(Object.assign({}, point, {
type: valueType,
value
}));
} else {
points.push(point);
}
});
this.props.onChange(Object.assign({}, this.props.value, {points}));
}
}
opacityChange (opacity) {
const type = this.props.value && this.props.value.type || 'hex';
if (type !== 'radial' && type !== 'linear') {
this.props.onChange({
type: this.props.value && this.props.value.type || 'hex',
value: this.props.value && this.props.value.value || '#000000',
opacity
});
} else {
const editingPoint = Math.min(this.state.editingPoint, this.props.value.points.length);
const points = [];
forEach(this.props.value.points, (point, index) => {
if (index === editingPoint) {
points.push(Object.assign({}, point, {
opacity
}));
} else {
points.push(point);
}
});
this.props.onChange(Object.assign({}, this.props.value, {points}));
}
}
toggleOpened () {
this.setState({
opened: !this.state.opened
});
}
previousInputType () {
this.setState({
inputType: this.state.inputType === 0 ? INPUT_TYPES.length - 1 : this.state.inputType - 1
});
}
nextInputType () {
this.setState({
inputType: this.state.inputType === INPUT_TYPES.length - 1 ? 0 : this.state.inputType + 1
});
}
toggleAddingColor () {
this.setState({
addingColor: !this.state.addingColor
});
}
changeAddingColor (addingColorName) {
this.setState({
addingColorName
});
}
addColor () {
if (this.state.addingColorName) {
const color = getColor(this.props.value || {
type: 'hex',
value: '#000000',
opacity: 100
}, this.props.colors);
this.props.colorsActions
.addColor({
color: {
_id: 1,
label: 1,
value: 1
}
}, {
label: this.state.addingColorName,
value: color.colr.toHex()
})
.then((result) => {
this.selectColor(result.addColor._id);
this.setState({
addingColor: false,
addingColorName: ''
});
});
}
}
changeToSolid () {
if (this.props.value.type === 'linear' || this.props.value.type === 'radial') {
if (this.props.value.type === 'linear') {
this.previousLinear = this.props.value;
} else {
this.previousRadial = this.props.value;
}
this.props.onChange({
type: this.props.value.points[0].type,
value: this.props.value.points[0].value,
opacity: this.props.value.points[0].opacity
});
}
}
changeToLinear () {
if (this.props.value.type !== 'linear') {
this.setState({
editingPoint: 0
}, () => {
if (this.props.value.type !== 'radial' && this.previousLinear) {
this.props.onChange(this.previousLinear);
} else {
let points = [];
if (this.props.value.type === 'radial') {
points = this.props.value.points;
} else {
points = [
Object.assign({perc: 0}, this.props.value),
Object.assign({perc: 100}, this.props.value)
];
}
this.props.onChange({
type: 'linear',
angle: 0,
points
});
}
});
}
}
changeToRadial () {
if (this.props.value.type !== 'radial') {
if (this.props.value.type !== 'linear' && this.previousRadial) {
this.props.onChange(this.previousRadial);
} else {
let points = [];
if (this.props.value.type === 'linear') {
points = this.props.value.points;
} else {
points = [
Object.assign({perc: 0}, this.props.value),
Object.assign({perc: 100}, this.props.value)
];
}
this.props.onChange({
type: 'radial',
radius: 'fs',
center: {
top: 50,
left: 50
},
points
});
}
}
}
changeAngle (angle) {
this.props.onChange(Object.assign({}, this.props.value, {angle}));
}
changeRadius (radius) {
this.props.onChange(Object.assign({}, this.props.value, {radius}));
}
changeCenter (center) {
this.props.onChange(Object.assign({}, this.props.value, {center}));
}
changeEditingPoint (editingPoint) {
this.setState({
editingPoint
});
}
pointPercChange (editingPoint, perc) {
const points = [];
forEach(this.props.value.points, (point, index) => {
if (index === editingPoint) {
points.push(Object.assign({}, point, {
perc
}));
} else {
points.push(point);
}
});
this.props.onChange(Object.assign({}, this.props.value, {points}));
}
addPoint (perc) {
const orderedPoints = sortBy(this.props.value.points, 'perc');
let to = 0;
forEach(orderedPoints, (point, index) => {
if (point.perc < perc) {
to = index + 1;
} else {
return false;
}
});
let newPoint;
if (to === 0) {
newPoint = Object.assign({}, orderedPoints[0], {perc});
} else if (to === orderedPoints.length) {
newPoint = Object.assign({}, orderedPoints[orderedPoints.length - 1], {perc});
} else {
newPoint = Object.assign({}, orderedPoints[to - 1], {perc});
}
const newPoints = this.props.value.points.slice(0);
newPoints.splice(to, 0, newPoint);
this.props.onChange(Object.assign({}, this.props.value, {
points: newPoints
}));
this.setState({
editingPoint: to
});
}
removePoint (index) {
if (this.props.value.points.length > 2) {
const newPoints = this.props.value.points.slice(0);
newPoints.splice(index, 1);
this.props.onChange(Object.assign({}, this.props.value, {
points: newPoints
}));
this.setState({
editingPoint: 0
});
}
}
getCurrentColor () {
return getColor(this.props.value || {
type: 'hex',
value: '#000000',
opacity: 100
}, this.props.colors);
}
render () {
const type = this.props.value && this.props.value.type || 'hex';
let color;
if (type !== 'linear' && type !== 'radial') {
color = this.getCurrentColor();
} else {
color = getColor(
this.props.value.points[Math.min(this.state.editingPoint, this.props.value.points.length)],
this.props.colors
);
}
return (
);
}
}
================================================
FILE: lib/shared/screens/admin/shared/components/input-options/color/input.jsx
================================================
import cx from 'classnames';
import Component from 'components/component';
import React, {PropTypes} from 'react';
import styles from './input.less';
export default class Inputs extends Component {
static propTypes = {
small: PropTypes.bool,
value: PropTypes.string.isRequired,
label: PropTypes.string.isRequired,
onChange: PropTypes.func.isRequired
};
getInitState () {
return {
focused: false
};
}
onFocus () {
this.setState({
focused: true,
value: this.props.value
});
}
onBlur () {
this.setState({
focused: false
});
}
onChange (event) {
this.setState({
value: event.target.value
});
this.props.onChange(event.target.value);
}
render () {
const {small, value, label} = this.props;
return (
);
}
}
================================================
FILE: lib/shared/screens/admin/shared/components/input-options/color/input.less
================================================
@import '~styles/colors.less';
.input {
display: inline-block;
width: 70px;
margin: 0 4.5px;
}
.small {
width: 38px;
}
.inputField {
width: 100%;
text-align: center;
background-color: #ffffff;
border: 1px solid rgba(0, 0, 0, 0.2);
padding: 3.5px;
font-size: 12px;
line-height: 15px;
color: #999999;
&:focus {
border-color: @primary;
}
}
.label {
font-size: 8px;
color: #999999;
line-height: 17px;
}
================================================
FILE: lib/shared/screens/admin/shared/components/input-options/color/inputs.jsx
================================================
import utils from 'helpers/utils';
import Component from 'components/component';
import React, {PropTypes} from 'react';
import {hexIsValid} from 'helpers/colors';
import styles from './inputs.less';
import Input from './input';
export default class Inputs extends Component {
static propTypes = {
colr: PropTypes.object.isRequired,
opacity: PropTypes.number.isRequired,
previousInputType: PropTypes.func.isRequired,
nextInputType: PropTypes.func.isRequired,
inputType: PropTypes.string.isRequired,
hexChange: PropTypes.func.isRequired,
hsvChange: PropTypes.func.isRequired,
rgbChange: PropTypes.func.isRequired,
opacityChange: PropTypes.func.isRequired
};
init () {
this.onRChange = this.onRGBChange.bind(this, 'r');
this.onGChange = this.onRGBChange.bind(this, 'g');
this.onBChange = this.onRGBChange.bind(this, 'b');
this.onHChange = this.onHSVChange.bind(this, 'h');
this.onSChange = this.onHSVChange.bind(this, 's');
this.onVChange = this.onHSVChange.bind(this, 'v');
}
onHexChange (value) {
if (hexIsValid(value)) {
this.props.hexChange(value);
}
}
onRGBChange (prop, value) {
const number = parseInt(value, 10);
if (!isNaN(number)) {
const rgb = this.props.colr.toRgbObject();
rgb[prop] = utils.limitNumber(number, 0, 255);
this.props.rgbChange(rgb);
}
}
onHSVChange (prop, value) {
const number = parseInt(value, 10);
if (!isNaN(number)) {
const hsv = this.props.colr.toHsvObject();
if (prop === 'h') {
hsv[prop] = utils.limitNumber(number, 0, 360);
} else {
hsv[prop] = utils.limitNumber(number, 0, 100);
}
this.props.hsvChange(hsv);
}
}
onOpacityChange (value) {
const opacity = parseInt(value, 10);
if (!isNaN(opacity)) {
this.props.opacityChange(utils.limitNumber(opacity, 0, 100));
}
}
render () {
return (
{this.renderInputs()}
);
}
renderInputs () {
const {inputType, opacity, colr} = this.props;
if (inputType === 'hex') {
const hex = colr.toHex();
return (
);
} else if (inputType === 'rgba') {
const rgb = colr.toRgbObject();
return (
);
} else if (inputType === 'hsva') {
const hsv = colr.toHsvObject();
return (
);
}
}
}
================================================
FILE: lib/shared/screens/admin/shared/components/input-options/color/inputs.less
================================================
.root {
position: relative;
padding-top: 12px;
padding-bottom: 4px;
}
.previous, .next {
position: absolute;
display: inline-block;
top: 50%;
transform: translateY(-50%);
width: 12px;
height: 12px;
cursor: pointer;
:global i {
font-size: 12px;
color: #999999;
line-height: 12px;
}
&:hover :global i {
color: #333333;
}
}
.previous {
left: 0;
}
.next {
right: 0;
}
.inputs {
padding: 0 12px;
text-align: center;
}
================================================
FILE: lib/shared/screens/admin/shared/components/input-options/color/linear-gradient.jsx
================================================
import cx from 'classnames';
import sortBy from 'lodash.sortby';
import utils from 'helpers/utils';
import Component from 'components/component';
import React, {PropTypes} from 'react';
import {applyBackground, getColorString} from 'helpers/colors';
import styles from './linear-gradient.less';
export default class LinearGradient extends Component {
static propTypes = {
editingPoint: PropTypes.number.isRequired,
value: PropTypes.object.isRequired,
colors: PropTypes.array.isRequired,
changeEditingPoint: PropTypes.func.isRequired,
pointPercChange: PropTypes.func.isRequired,
addPoint: PropTypes.func.isRequired,
changeAngle: PropTypes.func.isRequired
};
constructor (props, children) {
super(props, children);
this.onMouseUpListener = this.onMouseUp.bind(this);
this.onMouseMoveListener = this.onMouseMove.bind(this);
}
getInitState () {
return {
dragging: false
};
}
componentWillUnmount () {
document.removeEventListener('mouseup', this.onMouseUpListener);
document.removeEventListener('mousemove', this.onMouseMoveListener);
}
markerClicked (number) {
this.props.changeEditingPoint(number);
}
onMouseDown (index, event) {
event.preventDefault();
event.stopPropagation();
const orderedPoints = sortBy(this.props.value.points, 'perc');
this.activePoint = index;
this.activeFirst = orderedPoints[0] === this.props.value.points[index];
this.activeLast = orderedPoints[orderedPoints.length - 1] === this.props.value.points[index];
this.setState({
dragging: true
});
document.addEventListener('mouseup', this.onMouseUpListener);
document.addEventListener('mousemove', this.onMouseMoveListener);
}
onMouseMove (event) {
event.preventDefault();
const bounds = utils.getOffsetRect(this.refs.holder);
const point = {
x: utils.limitNumber((event.pageX - bounds.left) / bounds.width, 0, 1),
y: 1 - utils.limitNumber((event.pageY - bounds.top) / bounds.height, 0, 1)
};
if (this.activeFirst || this.activeLast) {
// point.y = 1 - point.y;
const center = {
x: 0.5,
y: 0.5
};
const lineAngle = this.getLineAngle(center, point);
let newAngle;
if (lineAngle > 359 && lineAngle < 360) {
newAngle = 0;
} else {
newAngle = utils.roundSnap(lineAngle, [0, 45, 90, 135, 180, 225, 270, 315]);
}
if (this.activeFirst) {
newAngle -= 180;
if (newAngle < 0) {
newAngle += 360;
}
}
this.props.changeAngle(newAngle);
}
const pointA = this.getRectPoint(this.props.value.angle, 158);
const pointB = {
x: -pointA.x,
y: -pointA.y
};
const newPoint = {
x: ((point.x - 0.5) * 2) * 156,
y: ((point.y - 0.5) * 2) * 156
};
const xDelta = pointA.x - pointB.x;
const yDelta = pointA.y - pointB.y;
const u =
((newPoint.x - pointB.x) * xDelta + (newPoint.y - pointB.y) * yDelta) /
(xDelta * xDelta + yDelta * yDelta);
let closestPoint;
if (u < 0) {
closestPoint = {
x: pointB.x,
y: pointB.y
};
} else if (u > 1) {
closestPoint = {
x: pointA.x,
y: pointA.y
};
} else {
closestPoint = {
x: Math.round(pointB.x + u * xDelta),
y: Math.round(pointB.y + u * yDelta)
};
}
const total = utils.pointsDistance(pointA, pointB);
const dist = utils.pointsDistance(pointB, closestPoint);
this.props.pointPercChange(this.activePoint, utils.roundSnap(dist / total * 100, [0, 25, 50, 75, 100]));
}
onMouseUp (event) {
event.preventDefault();
event.stopPropagation();
this.componentWillUnmount();
this.setState({
dragging: false
});
}
onLineClick (event) {
event.preventDefault();
const bounds = utils.getOffsetRect(this.refs.holder);
const point = {
x: utils.limitNumber((event.pageX - bounds.left) / bounds.width, 0, 1),
y: 1 - utils.limitNumber((event.pageY - bounds.top) / bounds.height, 0, 1)
};
const pointA = this.getRectPoint(this.props.value.angle, 158);
const pointB = {
x: -pointA.x,
y: -pointA.y
};
const newPoint = {
x: ((point.x - 0.5) * 2) * 156,
y: ((point.y - 0.5) * 2) * 156
};
const total = utils.pointsDistance(pointA, pointB);
const dist = utils.pointsDistance(pointB, newPoint);
this.props.addPoint(Math.round(dist / total * 100));
}
getLineAngle (pointA, pointB) {
const dy = pointB.y - pointA.y;
const dx = pointB.x - pointA.x;
let theta = Math.atan2(dy, dx) * 180 / Math.PI; // range (-180, 180)
if (theta < 0) {
theta += 360;
}
return theta;
}
getRectPoint (angle, radius) {
const result = {
x: 0,
y: 0
};
if (angle >= 0 && angle <= 45) {
result.x = radius;
result.y = radius * Math.tan(angle * Math.PI / 180);
} else if (angle > 45 && angle <= 90) {
result.x = radius * Math.tan((45 - (angle - 45)) * Math.PI / 180);
result.y = radius;
} else if (angle > 90 && angle <= 180) {
const calc = this.getRectPoint(angle - 90, radius);
result.x = -calc.y;
result.y = calc.x;
} else if (angle > 180 && angle <= 270) {
const calc = this.getRectPoint(angle - 180, radius);
result.x = -calc.x;
result.y = -calc.y;
} else if (angle > 270 && angle <= 360) {
const calc = this.getRectPoint(angle - 270, radius);
result.x = calc.y;
result.y = -calc.x;
}
return result;
}
render () {
return (
{this.renderContent()}
);
}
renderContent () {
const gradStyle = {};
applyBackground(gradStyle, this.props.value, this.props.colors);
const angle = this.props.value.angle;
const pointA = this.getRectPoint(angle, 158);
const pointB = {
x: -pointA.x,
y: -pointA.y
};
// relative to html axis
pointA.x = pointA.x + 158;
pointA.y = 158 - pointA.y;
pointB.x = pointB.x + 158;
pointB.y = 158 - pointB.y;
const orderedPoints = sortBy(this.props.value.points, 'perc');
const firstPointPosition = utils.getPointInLineByPerc(
pointB,
pointA,
orderedPoints[0].perc
);
const lastPointPosition = utils.getPointInLineByPerc(
pointB,
pointA,
orderedPoints[orderedPoints.length - 1].perc
);
return (
{this.props.value.points.map(this.renderPoint.bind(this, pointA, pointB))}
{this.renderAngle()}
);
}
renderPoint (pointA, pointB, colorObj, index) {
const pointPosition = utils.getPointInLineByPerc(pointB, pointA, colorObj.perc);
const selected = this.props.editingPoint === index;
const style = {
left: pointPosition.x,
top: pointPosition.y,
backgroundColor: getColorString(colorObj, this.props.colors)
};
const onClick = this.markerClicked.bind(this, index);
const onMouseDown = this.onMouseDown.bind(this, index);
return (
);
}
renderAngle () {
if (this.state.dragging && (this.activeFirst || this.activeLast)) {
const angle = this.props.value.angle;
return (
{`${angle}º`}
);
}
}
}
================================================
FILE: lib/shared/screens/admin/shared/components/input-options/color/linear-gradient.less
================================================
.root {
position: absolute;
display: inline-block;
top: 30px;
width: 316px;
height: 316px;
left: -336px;
background-color: #ffffff;
-webkit-box-shadow: 0px 5px 25px 0px rgba(0,0,0,0.5);
-moz-box-shadow: 0px 5px 25px 0px rgba(0,0,0,0.5);
box-shadow: 0px 5px 25px 0px rgba(0,0,0,0.5);
&:before {
content: '';
position: absolute;
top: 0; right: 0; bottom: 0; left: 0;
opacity: 0.5;
background: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAIAAABLbSncAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAChJREFUeNpiPHPmDAMMGBsbw9lMDDgA6RKM%2F%2F%2F%2Fh3POnj1LCzsAAgwAQtYIcFfEyzkAAAAASUVORK5CYII%3D');
}
}
.content {
position: absolute;
top: 0; right: 0; bottom: 0; left: 0;
}
.lineSVG {
position: absolute;
width: 100%;
height: 100%;
top: 0;
left: 0;
-webkit-filter: drop-shadow(0px 0px 1px rgba(0,0,0,0.24));
filter: drop-shadow(0px 0px 1px rgba(0,0,0,0.24));
}
.line {
cursor: copy;
}
.point {
position: absolute;
display: inline-block;
width: 10px;
height: 10px;
border: 2px solid #ffffff;
transform: translate(-50%, -50%);
border-radius: 5px;
cursor: pointer;
-webkit-box-shadow: 0px 0px 1px 1px rgba(0,0,0,0.22);
-moz-box-shadow: 0px 0px 1px 1px rgba(0,0,0,0.22);
box-shadow: 0px 0px 1px 1px rgba(0,0,0,0.22);
&.selected {
width: 14px;
height: 14px;
border-radius: 7px;
}
}
.angleInfo {
position: absolute;
top: 50%; left: 50%;
transform: translate(-50%, -50%);
background-color: #333333;
width: 22px;
height: 22px;
border-radius: 11px;
text-align: center;
display: inline-block;
color: #efefef;
font-size: 10px;
line-height: 22px;
}
================================================
FILE: lib/shared/screens/admin/shared/components/input-options/color/opacity.jsx
================================================
import utils from 'helpers/utils';
import Component from 'components/component';
import React, {PropTypes} from 'react';
import styles from './opacity.less';
export default class Opacity extends Component {
static propTypes = {
opacity: PropTypes.number.isRequired,
opacityChange: PropTypes.func.isRequired,
colr: PropTypes.object.isRequired
};
constructor (props, children) {
super(props, children);
this.onMouseUpListener = this.onMouseUp.bind(this);
this.onMouseMoveListener = this.onMouseMove.bind(this);
}
componentWillUnmount () {
document.removeEventListener('mouseup', this.onMouseUpListener);
document.removeEventListener('mousemove', this.onMouseMoveListener);
}
onMouseDown (event) {
event.preventDefault();
event.stopPropagation();
this.onMouseMove(event);
document.addEventListener('mouseup', this.onMouseUpListener);
document.addEventListener('mousemove', this.onMouseMoveListener);
}
onMouseMove (event) {
event.preventDefault();
const bounds = utils.getOffsetRect(this.refs.bar);
const perc = utils.limitNumber((event.pageX - bounds.left) / bounds.width, 0, 1);
this.props.opacityChange(Math.round(perc * 100));
}
onMouseUp (event) {
event.preventDefault();
event.stopPropagation();
this.componentWillUnmount();
}
render () {
const {opacity, colr} = this.props;
const markerStyle = {
left: `${opacity}%`,
transform: `translate(${-opacity}%, -50%)`
};
const rgb = colr.toRgbObject();
const gradStyle = {
background:
`linear-gradient(to right, rgba(${rgb.r},${rgb.g},${rgb.b},0) 0%,rgba(${rgb.r},${rgb.g},${rgb.b},1) 100%)`
};
return (
);
}
}
================================================
FILE: lib/shared/screens/admin/shared/components/input-options/color/opacity.less
================================================
.root {
width: 100%;
height: 10px;
margin-top: 4px;
position: relative;
}
.opacity {
position: absolute;
top: 0;
left: 0;
bottom: 0;
right: 14px;
border-radius: 4px;
&:before {
content: '';
position: absolute;
top: 0; right: 0; bottom: 0; left: 0;
opacity: 0.5;
background: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAIAAABLbSncAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAChJREFUeNpiPHPmDAMMGBsbw9lMDDgA6RKM%2F%2F%2F%2Fh3POnj1LCzsAAgwAQtYIcFfEyzkAAAAASUVORK5CYII%3D');
}
&:after {
content: '';
position: absolute;
display: block;
border-radius: 4px;
top: 0px; right: 0px; bottom: 0px; left: 0px;
border: 1px solid rgba(0, 0, 0, 0.2);
}
}
.gradient {
position: absolute;
top: 0;
left: 0;
bottom: 0;
right: 0;
border-radius: 4px;
background: -moz-linear-gradient(left, rgba(255,0,0,0) 0%, rgba(255,0,0,1) 100%);
background: -webkit-linear-gradient(left, rgba(255,0,0,0) 0%,rgba(255,0,0,1) 100%);
background: linear-gradient(to right, rgba(255,0,0,0) 0%,rgba(255,0,0,1) 100%);
}
.marker {
position: absolute;
display: inline-block;
width: 8px;
height: 12px;
border: 1px solid rgba(0, 0, 0, 0.4);
background-color: #ffffff;
border-radius: 2px;
z-index: 1;
transform: translate(-50%, -50%);
top: 50%;
left: 50%;
cursor: pointer;
}
================================================
FILE: lib/shared/screens/admin/shared/components/input-options/color/radial-gradient.jsx
================================================
import cx from 'classnames';
import forEach from 'lodash.foreach';
import sortBy from 'lodash.sortby';
import utils from 'helpers/utils';
import Component from 'components/component';
import React, {PropTypes} from 'react';
import {applyBackground, getColorString} from 'helpers/colors';
import styles from './radial-gradient.less';
const size = 316;
export default class RadialGradient extends Component {
static propTypes = {
editingPoint: PropTypes.number.isRequired,
value: PropTypes.object.isRequired,
colors: PropTypes.array.isRequired,
changeEditingPoint: PropTypes.func.isRequired,
pointPercChange: PropTypes.func.isRequired,
addPoint: PropTypes.func.isRequired,
changeCenter: PropTypes.func.isRequired
};
constructor (props, children) {
super(props, children);
this.onMouseUpListener = this.onMouseUp.bind(this);
this.onMouseMoveListener = this.onMouseMove.bind(this);
}
getInitState () {
return {
dragging: false
};
}
componentWillUnmount () {
document.removeEventListener('mouseup', this.onMouseUpListener);
document.removeEventListener('mousemove', this.onMouseMoveListener);
}
markerClicked (number) {
this.props.changeEditingPoint(number);
}
onMouseDown (index, event) {
event.preventDefault();
event.stopPropagation();
const orderedPoints = sortBy(this.props.value.points, 'perc');
this.activePoint = index;
this.activeFirst = orderedPoints[0] === this.props.value.points[index];
this.activeLast = orderedPoints[orderedPoints.length - 1] === this.props.value.points[index];
this.setState({
dragging: true
});
document.addEventListener('mouseup', this.onMouseUpListener);
document.addEventListener('mousemove', this.onMouseMoveListener);
}
onMouseMove (event) {
event.preventDefault();
const bounds = utils.getOffsetRect(this.refs.holder);
const point = {
x: utils.limitNumber((event.pageX - bounds.left) / bounds.width, 0, 1),
y: utils.limitNumber((event.pageY - bounds.top) / bounds.height, 0, 1)
};
if (this.activeFirst) {
this.props.changeCenter({
top: utils.roundSnap(point.y * 100, [0, 25, 50, 75, 100]),
left: utils.roundSnap(point.x * 100, [0, 25, 50, 75, 100])
});
} else {
const {pointA, pointB} = this.getRadialLine();
const newPoint = {
x: point.x * size,
y: point.y * size
};
const xDelta = pointA.x - pointB.x;
const yDelta = pointA.y - pointB.y;
const u =
1 -
((newPoint.x - pointB.x) * xDelta + (newPoint.y - pointB.y) * yDelta) /
(xDelta * xDelta + yDelta * yDelta);
let closestPoint;
if (u < 0) {
closestPoint = {
x: pointB.x,
y: pointB.y
};
} else if (u > 1) {
closestPoint = {
x: pointA.x,
y: pointA.y
};
} else {
closestPoint = {
x: Math.round(pointB.x + u * xDelta),
y: Math.round(pointB.y + u * yDelta)
};
}
const total = utils.pointsDistance(pointA, pointB);
const dist = utils.pointsDistance(pointB, closestPoint);
this.props.pointPercChange(this.activePoint, utils.roundSnap(dist / total * 100, [0, 25, 50, 75, 100]));
}
}
onMouseUp (event) {
event.preventDefault();
event.stopPropagation();
this.componentWillUnmount();
this.setState({
dragging: false
});
}
onLineClick (event) {
event.preventDefault();
const bounds = utils.getOffsetRect(this.refs.holder);
const point = {
x: utils.limitNumber((event.pageX - bounds.left) / bounds.width, 0, 1),
y: utils.limitNumber((event.pageY - bounds.top) / bounds.height, 0, 1)
};
const {pointA, pointB} = this.getRadialLine();
const newPoint = {
x: point.x * size,
y: point.y * size
};
const total = utils.pointsDistance(pointA, pointB);
const dist = utils.pointsDistance(pointA, newPoint);
this.props.addPoint(Math.round(dist / total * 100));
}
getRadialLine () {
const radius = this.props.value.radius;
const center = this.props.value.center;
const pointA = {
x: size * center.left / 100,
y: size * center.top / 100
};
let pointB;
if (radius[1] === 'c') {
let closestPoint;
let closestDistance = 999;
let farthestPoint;
let farthestDistance = 0;
const corners = [
{x: 0, y: 0},
{x: size, y: 0},
{x: size, y: size},
{x: 0, y: size}
];
forEach(corners, (cornerPoint) => {
const distance = utils.pointsDistance(pointA, cornerPoint);
if (distance > farthestDistance) {
farthestDistance = distance;
farthestPoint = cornerPoint;
}
if (distance < closestDistance) {
closestDistance = distance;
closestPoint = cornerPoint;
}
});
if (radius === 'fc') {
pointB = farthestPoint;
} else {
pointB = closestPoint;
}
} else {
let closestPoint;
let closestDistance = 999;
let farthestPoint;
let farthestDistance = 0;
const sides = [
{x: 0, y: pointA.y},
{x: pointA.x, y: 0},
{x: size, y: pointA.y},
{x: pointA.x, y: size}
];
forEach(sides, (sidePoint) => {
const distance = utils.pointsDistance(pointA, sidePoint);
if (distance > farthestDistance) {
farthestDistance = distance;
farthestPoint = sidePoint;
}
if (distance < closestDistance) {
closestDistance = distance;
closestPoint = sidePoint;
}
});
if (radius === 'fs') {
pointB = farthestPoint;
} else {
pointB = closestPoint;
}
}
return {
pointA,
pointB
};
}
render () {
return (
{this.renderContent()}
);
}
renderContent () {
const gradStyle = {};
applyBackground(gradStyle, this.props.value, this.props.colors);
const radialLine = this.getRadialLine();
const orderedPoints = sortBy(this.props.value.points, 'perc');
const firstPointPosition = utils.getPointInLineByPerc(
radialLine.pointA,
radialLine.pointB,
orderedPoints[0].perc
);
const lastPointPosition = utils.getPointInLineByPerc(
radialLine.pointA,
radialLine.pointB,
orderedPoints[orderedPoints.length - 1].perc
);
return (
{this.props.value.points.map(this.renderPoint.bind(this, radialLine.pointA, radialLine.pointB))}
);
}
renderPoint (pointA, pointB, colorObj, index) {
const pointPosition = utils.getPointInLineByPerc(pointA, pointB, colorObj.perc);
const selected = this.props.editingPoint === index;
const style = {
left: pointPosition.x,
top: pointPosition.y,
backgroundColor: getColorString(colorObj, this.props.colors)
};
const onClick = this.markerClicked.bind(this, index);
const onMouseDown = this.onMouseDown.bind(this, index);
return (
);
}
}
================================================
FILE: lib/shared/screens/admin/shared/components/input-options/color/radial-gradient.less
================================================
.root {
position: absolute;
display: inline-block;
top: 30px;
width: 316px;
height: 316px;
left: -336px;
background-color: #ffffff;
-webkit-box-shadow: 0px 5px 25px 0px rgba(0,0,0,0.5);
-moz-box-shadow: 0px 5px 25px 0px rgba(0,0,0,0.5);
box-shadow: 0px 5px 25px 0px rgba(0,0,0,0.5);
&:before {
content: '';
position: absolute;
top: 0; right: 0; bottom: 0; left: 0;
opacity: 0.5;
background: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAIAAABLbSncAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAChJREFUeNpiPHPmDAMMGBsbw9lMDDgA6RKM%2F%2F%2F%2Fh3POnj1LCzsAAgwAQtYIcFfEyzkAAAAASUVORK5CYII%3D');
}
}
.content {
position: absolute;
top: 0; right: 0; bottom: 0; left: 0;
}
.lineSVG {
position: absolute;
width: 100%;
height: 100%;
top: 0;
left: 0;
-webkit-filter: drop-shadow(0px 0px 1px rgba(0,0,0,0.24));
filter: drop-shadow(0px 0px 1px rgba(0,0,0,0.24));
}
.line {
cursor: copy;
}
.point {
position: absolute;
display: inline-block;
width: 10px;
height: 10px;
border: 2px solid #ffffff;
transform: translate(-50%, -50%);
border-radius: 5px;
cursor: pointer;
-webkit-box-shadow: 0px 0px 1px 1px rgba(0,0,0,0.22);
-moz-box-shadow: 0px 0px 1px 1px rgba(0,0,0,0.22);
box-shadow: 0px 0px 1px 1px rgba(0,0,0,0.22);
&.selected {
width: 14px;
height: 14px;
border-radius: 7px;
}
}
================================================
FILE: lib/shared/screens/admin/shared/components/input-options/color/radial-radius.jsx
================================================
import cx from 'classnames';
import Component from 'components/component';
import React, {PropTypes} from 'react';
import styles from './radial-radius.less';
export default class RadialRadius extends Component {
static propTypes = {
radius: PropTypes.string.isRequired,
changeRadius: PropTypes.func.isRequired
};
init () {
this.onCCClick = this.onClick.bind(this, 'cc');
this.onFCClick = this.onClick.bind(this, 'fc');
this.onCSClick = this.onClick.bind(this, 'cs');
this.onFSClick = this.onClick.bind(this, 'fs');
}
onClick (radius, event) {
event.preventDefault();
this.props.changeRadius(radius);
}
render () {
const radialCenterX = 6.25;
const radialCenterY = 9;
const {radius} = this.props;
return (
);
}
}
================================================
FILE: lib/shared/screens/admin/shared/components/input-options/color/radial-radius.less
================================================
.root {
border-top: 2px solid #dbdbdb;
padding: 10px 0;
}
.label {
display: inline-block;
vertical-align: top;
font-size: 10px;
text-transform: uppercase;
color: #999999;
line-height: 18px;
margin-right: 13px;
}
.option {
position: relative;
display: inline-block;
vertical-align: top;
cursor: pointer;
width: 25px;
height: 18px;
margin-right: 8px;
:global svg {
width: 100%;
height: 100%;
}
&:after {
content: '';
position: absolute;
top: 0; right: 0; bottom: 0; left: 0;
border: 1px solid rgba(0, 0, 0, 0.4);
}
}
.cc {
background: radial-gradient(circle closest-corner at 25% 50% , #848484, #ededed 100%);
}
.fc {
background: radial-gradient(circle farthest-corner at 25% 50% , #848484, #ededed 100%);
}
.cs {
background: radial-gradient(circle closest-side at 25% 50% , #848484, #ededed 100%);
}
.fs {
background: radial-gradient(circle farthest-side at 25% 50% , #848484, #ededed 100%);
}
.active {
&.cc {
background: radial-gradient(circle closest-corner at 25% 50% , #ffffff, #a0dbff 100%);
}
&.fc {
background: radial-gradient(circle farthest-corner at 25% 50% , #ffffff, #a0dbff 100%);
}
&.cs {
background: radial-gradient(circle closest-side at 25% 50% , #ffffff, #a0dbff 100%);
}
&.fs {
background: radial-gradient(circle farthest-side at 25% 50% , #ffffff, #a0dbff 100%);
}
}
================================================
FILE: lib/shared/screens/admin/shared/components/input-options/color/sat-light.jsx
================================================
import utils from 'helpers/utils';
import Colr from 'colr';
import Component from 'components/component';
import React, {PropTypes} from 'react';
import {findDOMNode} from 'react-dom';
import styles from './sat-light.less';
export default class SatLight extends Component {
static propTypes = {
colr: PropTypes.object.isRequired,
hsv: PropTypes.object.isRequired,
hsvChange: PropTypes.func.isRequired
};
constructor (props, children) {
super(props, children);
this.onMouseUpListener = this.onMouseUp.bind(this);
this.onMouseMoveListener = this.onMouseMove.bind(this);
}
componentWillUnmount () {
document.removeEventListener('mouseup', this.onMouseUpListener);
document.removeEventListener('mousemove', this.onMouseMoveListener);
}
onMouseDown (event) {
event.preventDefault();
event.stopPropagation();
this.onMouseMove(event);
document.addEventListener('mouseup', this.onMouseUpListener);
document.addEventListener('mousemove', this.onMouseMoveListener);
}
onMouseMove (event) {
event.preventDefault();
const bounds = utils.getOffsetRect(findDOMNode(this));
const percY = utils.limitNumber((event.pageY - bounds.top) / bounds.height, 0, 1);
const percX = utils.limitNumber((event.pageX - bounds.left) / bounds.width, 0, 1);
this.props.hsvChange({
h: this.props.hsv.h,
s: percX * 100,
v: 100 - percY * 100
});
}
onMouseUp (event) {
event.preventDefault();
event.stopPropagation();
this.componentWillUnmount();
}
render () {
const {hsv} = this.props;
const markerStyle = {
top: `${100 - hsv.v}%`,
left: `${hsv.s}%`
};
const backStyle = {
backgroundColor: Colr.fromHsv(hsv.h, 100, 100).toHex()
};
if (hsv.v > 50 && hsv.s < 50) {
markerStyle.borderColor = '#000000';
}
return (
);
}
}
================================================
FILE: lib/shared/screens/admin/shared/components/input-options/color/sat-light.less
================================================
.root {
position: absolute;
top: 0;
left: 0;
bottom: 0;
right: 14px;
background-color: #ff0000;
border-radius: 4px;
&:before, &:after {
content: '';
position: absolute;
display: block;
border-radius: 4px;
top: 0px; right: 0px; bottom: 0px; left: 0px;
}
&:before {
background: -moz-linear-gradient(left, rgba(255,255,255,1) 0%, rgba(255,255,255,0) 100%);
background: -webkit-linear-gradient(left, rgba(255,255,255,1) 0%,rgba(255,255,255,0) 100%);
background: linear-gradient(to right, rgba(255,255,255,1) 0%,rgba(255,255,255,0) 100%);
}
&:after {
background: -moz-linear-gradient(top, rgba(0,0,0,0) 0%, rgba(0,0,0,1) 100%);
background: -webkit-linear-gradient(top, rgba(0,0,0,0) 0%,rgba(0,0,0,1) 100%);
background: linear-gradient(to bottom, rgba(0,0,0,0) 0%,rgba(0,0,0,1) 100%);
}
}
.border {
position: absolute;
display: block;
border-radius: 4px;
z-index: 1;
top: 0px; right: 0px; bottom: 0px; left: 0px;
border: 1px solid rgba(0, 0, 0, 0.2);
}
.marker {
position: absolute;
display: inline-block;
width: 10px;
height: 10px;
border: 2px solid #ffffff;
border-radius: 5px;
z-index: 1;
transform: translate(-50%, -50%);
top: 50%;
left: 50%;
cursor: pointer;
}
================================================
FILE: lib/shared/screens/admin/shared/components/input-options/color/types.jsx
================================================
import cx from 'classnames';
import Component from 'components/component';
import React, {PropTypes} from 'react';
import styles from './types.less';
export default class Types extends Component {
static propTypes = {
type: PropTypes.string.isRequired,
changeToSolid: PropTypes.func.isRequired,
changeToLinear: PropTypes.func.isRequired,
changeToRadial: PropTypes.func.isRequired
};
render () {
const {type} = this.props;
return (
);
}
}
================================================
FILE: lib/shared/screens/admin/shared/components/input-options/color/types.less
================================================
@import '~styles/colors.less';
.types {
text-align: right;
margin-bottom: 6px;
}
.type {
display: inline-block;
position: relative;
width: 26px;
height: 26px;
margin-left: 16px;
border-radius: 4px;
cursor: pointer;
&:after {
content: ' ';
position: absolute;
top: 0; left: 0; right: 0; bottom: 0;
border: 1px solid rgba(0, 0, 0, 0.20);
border-radius: 4px;
}
&:hover {
&:after {
border-color: @primary;
}
}
}
.solid {
background-color: #999999;
&.active {
background-color: @primary;
&:after {
border-color: @primary;
}
}
}
.linear {
background: -moz-linear-gradient(top, #ffffff 0%, #999999 100%);
background: -webkit-linear-gradient(top, #ffffff 0%,#999999 100%);
background: linear-gradient(to bottom, #ffffff 0%,#999999 100%);
&.active {
background: -moz-linear-gradient(top, #ffffff 0%, @primary 100%);
background: -webkit-linear-gradient(top, #ffffff 0%, @primary 100%);
background: linear-gradient(to bottom, #ffffff 0%, @primary 100%);
&:after {
border-color: @primary;
}
}
}
.radial {
background: -moz-radial-gradient(center, ellipse cover, #ffffff 0%, #999999 100%);
background: -webkit-radial-gradient(center, ellipse cover, #ffffff 0%,#999999 100%);
background: radial-gradient(ellipse at center, #ffffff 0%,#999999 100%);
&.active {
background: -moz-radial-gradient(center, ellipse cover, #ffffff 0%, @primary 100%);
background: -webkit-radial-gradient(center, ellipse cover, #ffffff 0%, @primary 100%);
background: radial-gradient(ellipse at center, #ffffff 0%, @primary 100%);
&:after {
border-color: @primary;
}
}
}
================================================
FILE: lib/shared/screens/admin/shared/components/input-options/columns/column.jsx
================================================
import bind from 'decorators/bind';
import cx from 'classnames';
import Component from 'components/component';
import React, {PropTypes} from 'react';
import styles from './column.less';
export default class Column extends Component {
static propTypes = {
selected: PropTypes.bool.isRequired,
width: PropTypes.string,
widthPerc: PropTypes.string,
id: PropTypes.string.isRequired,
onClick: PropTypes.func.isRequired,
className: PropTypes.string
};
@bind
onClick () {
const {id, onClick} = this.props;
onClick(id);
}
render () {
const {selected, width, widthPerc, className} = this.props;
const style = {};
if (width === 'custom') {
style.width = `${widthPerc}`;
}
return (
);
}
}
================================================
FILE: lib/shared/screens/admin/shared/components/input-options/columns/column.less
================================================
@import '~styles/colors.less';
.root {
position: relative;
cursor: pointer;
height: 22px;
&:after {
content: " ";
position: absolute;
top: 2px;
right: 2px;
bottom: 2px;
left: 2px;
background-color: @chromeBackgroundDarkerColor;
border: 1px solid @chromeBordersColor;
border-radius: 3px;
}
&:hover {
&:after {
background-color: #ccc;
}
}
&.active {
&:after {
background-color: @primary;
}
}
}
================================================
FILE: lib/shared/screens/admin/shared/components/input-options/columns/columns-manager.jsx
================================================
import bind from 'decorators/bind';
import cloneDeep from 'lodash.clonedeep';
import utils from 'helpers/utils';
import Component from 'components/component';
import React, {PropTypes} from 'react';
import styles from './columns-manager.less';
import Column from './column';
export default class ColumnsManager extends Component {
static propTypes = {
value: PropTypes.array.isRequired,
onChange: PropTypes.func.isRequired,
OptionsList: PropTypes.any.isRequired,
multiRows: PropTypes.bool,
selectedElement: PropTypes.object,
columnOptions: PropTypes.array.isRequired,
columnOptionsSingleRow: PropTypes.array.isRequired,
breakOptions: PropTypes.array.isRequired
};
static defaultProps = {
multiRows: true
};
getInitState () {
return {
selected: false
};
}
parseValue (value, idChanged = -1) {
const {selectedElement, multiRows} = this.props;
return utils.parseColumnsDisplay(value, selectedElement.children.length, multiRows, idChanged);
}
@bind
onClick (key) {
if (this.state.selected === key) {
this.setState({
selected: false
});
} else {
this.setState({
selected: key
});
}
}
onChange (id, value) {
const valueParsed = cloneDeep(this.parseValue(this.props.value));
valueParsed[this.state.selected][id] = value;
const result = this.parseValue(valueParsed, this.state.selected);
this.props.onChange(result);
}
render () {
return (
{this.renderChildren()}
{this.renderOptions()}
);
}
renderChildren () {
const {value, selectedElement} = this.props;
const valueParsed = this.parseValue(value);
const children = [];
const numChildren = selectedElement.children.length;
let i;
for (i = 0; i < numChildren; i++) {
if (valueParsed[i].width === 'block') {
children.push(this.renderColumn(i, valueParsed[i]));
} else {
const columns = [];
for (i; i < numChildren; i++) {
if (valueParsed[i].width !== 'block' && !(columns.length > 0 && valueParsed[i].break)) {
columns.push(this.renderColumn(i, valueParsed[i]));
} else {
i--;
break;
}
}
children.push(
{columns}
);
}
}
return children;
}
renderColumn (id, value) {
return (
);
}
renderOptions () {
if (this.state.selected !== false) {
const {multiRows, OptionsList, columnOptions, columnOptionsSingleRow, breakOptions} = this.props;
const value = this.parseValue(this.props.value);
const values = value[this.state.selected];
const breakable = (
multiRows &&
values.width !== 'block' &&
this.state.selected >= 2 &&
this.state.selected < value.length - 1 &&
value[this.state.selected - 1].width !== 'block' &&
value[this.state.selected - 2].width !== 'block'
);
return (
{breakable && }
);
}
}
}
================================================
FILE: lib/shared/screens/admin/shared/components/input-options/columns/columns-manager.less
================================================
@import '~styles/colors.less';
.row {
display: table;
table-layout: fixed;
width: 100%;
.column {
display: table-cell;
vertical-align: top;
}
}
.options {
margin: 5px 0;
padding: 10px;
border-left: 2px solid @primary;
}
================================================
FILE: lib/shared/screens/admin/shared/components/input-options/columns/index.js
================================================
import Component from 'components/component';
import React from 'react';
import {connect} from 'react-redux';
import ColumnsManager from './columns-manager';
@connect(
(state) => ({
selectedElement: state.pageBuilder.selectedElement
})
)
export default class ColumnsManagerContainer extends Component {
static columnOptions = [
{
label: 'Width',
type: 'Select',
id: 'width',
props: {
labels: ['Block', 'Column auto', 'Column custom'],
values: ['block', 'auto', 'custom']
},
unlocks: {
custom: [
{
label: 'Width (%)',
type: 'Percentage',
id: 'widthPerc'
}
]
}
}
];
static columnOptionsSingleRow = [
{
label: 'Width',
type: 'Select',
id: 'width',
props: {
labels: ['Column auto', 'Column custom'],
values: ['auto', 'custom']
},
unlocks: {
custom: [
{
label: 'Width (%)',
type: 'Percentage',
id: 'widthPerc'
}
]
}
}
];
static breakOptions = [
{
label: 'To next line',
type: 'Boolean',
id: 'break',
default: false
}
];
render () {
return (
);
}
}
================================================
FILE: lib/shared/screens/admin/shared/components/input-options/combobox/index.jsx
================================================
import bind from 'decorators/bind';
import cx from 'classnames';
import forEach from 'lodash.foreach';
import Component from 'components/component';
import React from 'react';
import styles from './index.less';
export default class Combobox extends Component {
static propTypes = {
labels: React.PropTypes.array,
values: React.PropTypes.array.isRequired,
value: React.PropTypes.string.isRequired,
onChange: React.PropTypes.func.isRequired,
className: React.PropTypes.string,
style: React.PropTypes.object,
white: React.PropTypes.bool
};
getInitState () {
return {
opened: false
};
}
@bind
toggle () {
this.setState({
opened: !this.state.opened
});
}
optionClicked (value, event) {
event.preventDefault();
if (this.props.onChange) {
this.props.onChange(value);
}
this.setState({
opened: false
});
}
render () {
const {values, labels, value, className, style, white} = this.props;
let label = '';
forEach(values, (valueIt, key) => {
if (value === valueIt) {
label = labels && labels[key] || valueIt;
}
});
return (
{(labels || values).map(this.renderOption, this)}
);
}
renderOption (option, i) {
const onClick = this.optionClicked.bind(this, this.props.values[i]);
return (
{option}
);
}
}
================================================
FILE: lib/shared/screens/admin/shared/components/input-options/combobox/index.less
================================================
@import '~styles/colors.less';
@comboboxHeight: 38px;
.combobox {
position: relative;
display: inline-block;
cursor: pointer;
width: 100%;
max-width: 480px;
font-size: 10px;
&:hover {
.holder {
border-color: @primary;
}
}
&.small {
max-width: 120px;
}
}
.holder {
height: @comboboxHeight;
position: relative;
border: solid 1px @chromeBordersColor;
background-color: @chromeBackgroundDarkerColor;
overflow: hidden;
border-radius: 3px;
transition: all 0.3s ease-out;
.button {
color: @chromeTextColor;
text-align: center;
}
&.opened {
height: auto;
}
}
.header {
overflow: hidden;
.selectedText {
position: relative;
padding: 0px 12px;
line-height: @comboboxHeight;
font-size: 10px;
display: inline-block;
float: left;
color: @chromeTextColor;
}
.button {
display: inline-block;
float: right;
width: @comboboxHeight;
height: @comboboxHeight;
:global i {
line-height: @comboboxHeight;
font-size: 11px;
}
}
}
.options {
position: relative;
left: 0;
display: block;
max-height: 155px;
overflow-y: auto;
border-top: solid 1px @chromeBordersColor;
}
.option {
color: @chromeTextColor;
padding: 6px 12px;
display: block;
font-size: 10px;
transition: all 0.2s ease-out;
&:hover {
background-color: @primary;
color: @chromeTextColorHighlight;
}
}
// White areas
.white {
.holder {
background-color: transparent;
border-color: @adminInputBorders;
color: @adminText;
}
.header {
.selectedText {
color: @adminText;
}
.button {
color: @adminText;
border-color: @adminInputBorders;
}
}
.options {
border-color: @adminInputBorders;
}
.option {
color: @adminText;
&:hover {
color: @adminTextHighlight;
}
}
&:hover {
.combobox-holder {
border-color: @primary;
.button {
color: @adminText;
}
}
.header {
.button {
background-color: transparent;
}
}
}
}
================================================
FILE: lib/shared/screens/admin/shared/components/input-options/corners/index.jsx
================================================
import cx from 'classnames';
import Component from 'components/component';
import NumberInput from 'components/input-options/number';
import React from 'react';
export default class CornersPicker extends Component {
static propTypes = {
value: React.PropTypes.string.isRequired,
onChange: React.PropTypes.func.isRequired,
type: React.PropTypes.string.isRequired
};
getInitState () {
return {
selected: 'center',
values: this.parseValue(this.props.value)
};
}
componentWillReceiveProps (nextProps) {
this.setState({
values: this.parseValue(nextProps.value)
});
}
onInputChange (value) {
if (this.state.selected === 'center') {
this.state.values.tl = value;
this.state.values.tr = value;
this.state.values.br = value;
this.state.values.bl = value;
} else {
this.state.values[this.state.selected] = value;
}
this.props.onChange(this.getValuesString(this.state.values));
}
getValuesString (values) {
return `${values.tl || '0'}px ${values.tr || '0'}px ${values.br || '0'}px ${values.bl || '0'}px`;
}
parseValue (value) {
const values = value.split(' ');
const result = {
tl: 0,
bl: 0,
tr: 0,
br: 0,
equal: false
};
if (values.length === 1) {
const parsedValue = parseInt(values[0], 10);
result.tl = parsedValue;
result.br = parsedValue;
result.bl = parsedValue;
result.tr = parsedValue;
} else if (values.length === 2) {
result.tl = parseInt(values[0], 10);
result.tr = parseInt(values[1], 10);
result.br = parseInt(values[0], 10);
result.bl = parseInt(values[1], 10);
} else if (values.length === 4) {
result.tl = parseInt(values[0], 10);
result.tr = parseInt(values[1], 10);
result.br = parseInt(values[2], 10);
result.bl = parseInt(values[3], 10);
}
if (result.tl === result.tr && result.tl === result.br && result.tl === result.bl) {
result.equal = true;
} else {
result.equal = false;
}
return result;
}
changeSelected (selected, event) {
event.preventDefault();
this.setState({
selected
});
}
render () {
const values = this.state.values;
let inactive = false;
let value = 0;
if (this.state.selected !== 'center') {
value = values[this.state.selected];
} else {
inactive = !values.equal;
value = values.equal ? values.tl : Math.round((values.tl + values.tr + values.br + values.bl) / 4);
}
return (
{this.renderToggleButton('tl', !values.equal)}
{this.renderToggleButton('bl', !values.equal)}
{this.renderToggleButton('tr', !values.equal)}
{this.renderToggleButton('br', !values.equal)}
{this.renderToggleButton('center', values.equal)}
);
}
renderToggleButton (pos, active) {
const onClick = this.changeSelected.bind(this, pos);
return (
);
}
}
================================================
FILE: lib/shared/screens/admin/shared/components/input-options/filters/edit.jsx
================================================
import cx from 'classnames';
import find from 'lodash.find';
import forEach from 'lodash.foreach';
import getFilterDefaultOptions from 'helpers/schema-filter-default-options';
import Combobox from 'components/input-options/combobox';
import Component from 'components/component';
import Input from 'components/input-options/input';
import NumberInput from 'components/input-options/number';
import React, {PropTypes} from 'react';
export default class Edit extends Component {
static propTypes = {
schema: PropTypes.object,
filter: PropTypes.object.isRequired,
new: PropTypes.bool,
onPropertyChange: PropTypes.func.isRequired,
onOptionChange: PropTypes.func.isRequired,
cancelEdit: PropTypes.func.isRequired,
submitEdit: PropTypes.func.isRequired,
schemaProperties: PropTypes.array.isRequired
};
onOptionChange (key, value) {
this.props.onOptionChange(key, value);
}
render () {
const {filter, schemaProperties} = this.props;
const labels = [];
const values = [];
forEach(schemaProperties, (property) => {
labels.push(property.title);
values.push(property.id);
});
return (
cancel
{this.props.new ? 'Create new filter' : 'Ok'}
{this.renderOptions()}
);
}
renderOptions () {
let result;
const {filter, schemaProperties} = this.props;
const property = find(schemaProperties, 'id', filter.prop);
if (property) {
const options = Object.assign({}, getFilterDefaultOptions(property.type), filter.options);
switch (property.type) {
case 'Date':
result = ([
,
To
{
options.toGran !== 'present' &&
}
]);
break;
case 'String':
result = [
];
if (options.op === 'equal' || options.op === 'not-equal') {
result.push(
);
}
break;
case 'Boolean':
result = (
);
break;
default:
result = (
);
}
} else {
result = (
Invalid filter
);
}
return result;
}
}
================================================
FILE: lib/shared/screens/admin/shared/components/input-options/filters/filter.jsx
================================================
import find from 'lodash.find';
import getFilterDefaultOptions from 'helpers/schema-filter-default-options';
import Balloon from 'components/balloon';
import Component from 'components/component';
import React, {PropTypes} from 'react';
import Edit from './edit';
export default class Filter extends Component {
static propTypes = {
editing: PropTypes.bool.isRequired,
filter: PropTypes.object.isRequired,
schemaProperties: PropTypes.array.isRequired,
index: PropTypes.number.isRequired,
selectFilter: PropTypes.func.isRequired,
removeFilter: PropTypes.func.isRequired,
new: PropTypes.bool
};
getDateString (gran, value) {
let str;
if (gran === 'present') {
str = 'present';
} else {
str = `${value} ${gran}${value !== 1 && 's' || ''}`;
}
return str;
}
onClick () {
if (!this.props.new) {
this.props.selectFilter(this.props.index);
}
}
onRemove (event) {
event.preventDefault();
event.stopPropagation();
this.props.removeFilter(this.props.index);
}
render () {
return (
{
this.ref = ref;
!this.state.ready && this.setState({ready: true});
}}
>
{this.renderContent()}
{
!this.props.new &&
delete
}
{this.renderEditing()}
);
}
renderContent () {
let result;
const {filter, schemaProperties} = this.props;
const property = find(schemaProperties, 'id', filter.prop);
if (property) {
const options = Object.assign({}, getFilterDefaultOptions(property.type), filter.options);
switch (property.type) {
case 'Date':
result = (
{property.title}
from
{this.getDateString(options.fromGran, options.fromValue)}
up to
{this.getDateString(options.toGran, options.toValue)}
);
break;
case 'Boolean':
result = (
{property.title}
is
{options.value}
);
break;
default:
if (options.op === 'equal') {
result = (
{property.title}
equals
to
{options.value}
);
} else if (options.op === 'not-equal') {
result = (
{property.title}
differs
from
{options.value}
);
} else if (options.op === 'set') {
result = (
{property.title}
is
set
);
} else if (options.op === 'not-set') {
result = (
{property.title}
is
not set
);
}
}
}
return result;
}
renderEditing () {
if (this.props.editing && this.state.ready) {
return (
);
}
}
}
================================================
FILE: lib/shared/screens/admin/shared/components/input-options/filters/filters.jsx
================================================
import Component from 'components/component';
import React, {PropTypes} from 'react';
import Filter from './filter';
export default class Filters extends Component {
static fragments = {
schema: {
_id: 1,
title: 1,
properties: 1
}
};
static propTypes = {
value: PropTypes.string.isRequired,
openNew: PropTypes.func.isRequired,
newOpened: PropTypes.bool.isRequired,
schema: PropTypes.object.isRequired,
editingFilter: PropTypes.object,
editOpened: PropTypes.bool,
editIndex: PropTypes.number
};
render () {
return (
{this.props.value.map(this.renderFilter, this)}
{this.renderNew()}
Add new filter condition
);
}
renderFilter (filter, index) {
const editing = this.props.editOpened && this.props.editIndex === index;
return (
);
}
renderNew () {
if (this.props.newOpened) {
return (
);
}
}
}
================================================
FILE: lib/shared/screens/admin/shared/components/input-options/filters/index.js
================================================
import * as elementsActions from 'actions/elements';
import cloneDeep from 'lodash.clonedeep';
import forEach from 'lodash.foreach';
import staticProperties from 'helpers/schema-static-properties';
import Component from 'components/component';
import React, {PropTypes} from 'react';
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
import {buildQueryAndVariables} from 'relax-fragments';
import Filters from './filters';
@connect(
(state) => ({
elements: state.elements,
selectedId: state.pageBuilder.selectedId,
selectedElement: state.pageBuilder.selectedElement
}),
(dispatch) => bindActionCreators(elementsActions, dispatch)
)
export default class FiltersContainer extends Component {
static fragments = Filters.fragments;
static propTypes = {
value: PropTypes.array.isRequired,
onChange: PropTypes.func.isRequired,
elements: PropTypes.object.isRequired,
selectedId: PropTypes.string.isRequired,
selectedElement: PropTypes.object.isRequired
};
static defaultProps = {
value: []
};
getInitState () {
this.fetchData(this.props);
return {
newOpened: false
};
}
componentWillReceiveProps (nextProps) {
if (this.getSelectedSchemaId(nextProps) !== this.getSelectedSchemaId(this.props)) {
this.fetchData(nextProps);
}
}
getSelectedSchemaId (props) {
const {selectedElement} = props;
return selectedElement && selectedElement.props && selectedElement.props.schemaId;
}
fetchData (props) {
const {selectedId} = props;
const schemaId = this.getSelectedSchemaId(props);
if (schemaId) {
props.getElementData(selectedId, buildQueryAndVariables(
this.constructor.fragments,
{
schema: {
_id: {
value: schemaId,
type: 'ID!'
}
}
}
));
}
}
openNew () {
this.setState({
newOpened: true,
editingFilter: {
prop: 'publishedDate',
type: 'Date',
options: {}
}
});
}
onPropertyChange (prop) {
this.setState({
editingFilter: Object.assign({}, this.state.editingFilter, {
prop
})
});
}
onOptionChange (key, value) {
this.setState({
editingFilter: Object.assign({}, this.state.editingFilter, {
options: Object.assign({}, this.state.editingFilter.options, {
[key]: value
})
})
});
}
cancelEdit () {
this.setState({
newOpened: false,
editOpened: false,
editIndex: null
});
}
submitEdit () {
let newFilters = this.props.value;
if (this.state.newOpened) {
newFilters = [...newFilters, this.state.editingFilter];
} else if (this.state.editOpened) {
newFilters = [...newFilters];
newFilters[this.state.editIndex] = this.state.editingFilter;
}
this.props.onChange(newFilters);
this.setState({
newOpened: false,
editOpened: false,
editIndex: null
});
}
selectFilter (index) {
if (this.state.editOpened && this.state.editIndex === index) {
this.setState({
editOpened: false,
editIndex: null
});
} else {
this.setState({
editOpened: true,
editIndex: index,
editingFilter: cloneDeep(this.props.value[index])
});
}
}
removeFilter (index) {
const newFilters = [...this.props.value];
newFilters.splice(index, 1);
this.props.onChange(newFilters);
}
render () {
const {selectedId, elements} = this.props;
const elementData = elements[selectedId];
const schema = elementData && elementData.schema || null;
const schemaProperties = schema && schema.properties || [];
const properties = [...staticProperties];
forEach(schemaProperties, (customProperty) => {
properties.push(Object.assign({}, customProperty, {
id: `properties#${customProperty.id}`
}));
});
return (
);
}
}
================================================
FILE: lib/shared/screens/admin/shared/components/input-options/font/dropdown.jsx
================================================
import cx from 'classnames';
import Component from 'components/component';
import React, {PropTypes} from 'react';
import styles from './dropdown.less';
export default class Dropdown extends Component {
static propTypes = {
value: PropTypes.string.isRequired,
label: PropTypes.string.isRequired,
entries: PropTypes.array.isRequired,
onChange: PropTypes.func.isRequired,
tempChange: PropTypes.func.isRequired,
tempRevert: PropTypes.func.isRequired,
className: PropTypes.string
};
getInitState () {
return {
opened: false
};
}
onEntryClick (value, event) {
event.preventDefault();
this.props.onChange(value);
}
toggle () {
this.setState({
opened: !this.state.opened
});
}
render () {
const {className, label} = this.props;
return (
{this.renderCollapsable()}
{label}
);
}
renderCollapsable () {
if (this.state.opened) {
return (
{this.props.entries.map(this.renderEntry, this)}
);
}
}
renderEntry (entry) {
const onClick = this.onEntryClick.bind(this, entry.value);
return (
);
}
}
================================================
FILE: lib/shared/screens/admin/shared/components/input-options/font/dropdown.less
================================================
@import '~styles/colors.less';
.root {
position: relative;
display: inline-block;
margin: 0;
box-sizing: border-box;
text-decoration: none;
padding: 6px 8px;
vertical-align: top;
border-radius: 3px;
z-index: 1;
height: 30px;
font-size: 13px;
color: @chromeTextColor;
:global {
i {
content: "";
float: right;
width: 10px;
line-height: 21px;
position: relative;
}
}
&:hover{
background-color: @chromeBackgroundActive;
}
}
.info {
position: relative;
display: inline-block;
max-width: 125px;
width: 100%;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.collapsable {
position: absolute;
top: 0; left: 0; right: 0;
border: 1px solid @chromeBordersColor;
background-color: @chromeBackgroundActive;
padding-top: 29px;
margin-bottom: 30px;
border-radius: 3px;
}
.entry {
font-size: 13px;
padding: 5px 7px;
color: @chromeTextColor;
display: block;
width: 100%;
text-decoration: none;
border-top: 1px solid @chromeBordersColor;
text-align: left;
&:hover{
color: @primary;
background-color: @chromeBackgroundActive;
}
}
================================================
FILE: lib/shared/screens/admin/shared/components/input-options/font/font-picker.jsx
================================================
import clone from 'lodash.clone';
import forEach from 'lodash.foreach';
import Component from 'components/component';
import Utils from 'helpers/utils';
import React, {PropTypes} from 'react';
import styles from './font-picker.less';
import Dropdown from './dropdown';
import Font from './font';
export default class FontPicker extends Component {
static propTypes = {
value: PropTypes.string.isRequired,
onChange: PropTypes.func.isRequired,
fonts: PropTypes.object.isRequired
};
init () {
this.onFamilyChange = this.onChange.bind(this, 'family');
this.onFVDChange = this.onChange.bind(this, 'fvd');
}
getChangedValue (key, value) {
const newValue = clone(this.props.value || {});
newValue[key] = value;
if (key === 'family') {
// check if current fvd exists
const family = value;
const fvds = this.props.fonts.fonts[family];
if (fvds.indexOf(newValue.fvd) === -1) {
newValue.fvd = fvds[0];
}
}
return newValue;
}
onChange (key, value) {
const newValue = this.getChangedValue(key, value);
this.props.onChange(newValue);
}
render () {
return (
{this.renderFont()}
{this.renderOptions()}
);
}
renderOptions () {
const families = [];
const fvds = [];
const value = this.props.value;
if (this.props.fonts && typeof this.props.fonts.fonts === 'object') {
forEach(this.props.fonts.fonts, (fvdsArray, family) => {
families.push({
label: family,
value: family
});
if (value.family && value.family === family) {
forEach(fvdsArray, (fvd) => {
fvds.push({
label: fvd,
value: fvd
});
});
}
});
}
return (
);
}
renderFont () {
let result;
if (typeof this.props.value === 'object' && this.props.value.family && this.props.value.fvd) {
result = (
);
} else {
result = (
No font selected yet
);
}
return result;
}
}
================================================
FILE: lib/shared/screens/admin/shared/components/input-options/font/font-picker.less
================================================
@import '~styles/colors.less';
.root {
width: 100%;
border: 1px solid @chromeBordersColor;
border-radius: 3px;
}
.warning {
color: @chromeTextColor;
line-height: 77px;
text-align: center;
margin: 0;
}
.options {
background-color: @chromeBackgroundDarkerColor;
padding: 5px 5px;
position: relative;
height: 40px;
}
.fontsDropdown {
width: 67%;
margin-right: 3%;
text-transform: capitalize;
}
.typeDropdown {
width: 27%;
margin-left:3%;
}
.sep {
height: 24px;
width: 0;
border-left: 1px solid @chromeBordersColor;
position: absolute;
top: 8px;
}
================================================
FILE: lib/shared/screens/admin/shared/components/input-options/font/font.jsx
================================================
import utils from 'helpers/utils';
import Component from 'components/component';
import React from 'react';
import styles from './font.less';
export default class Font extends Component {
static propTypes = {
input: React.PropTypes.bool,
style: React.PropTypes.object,
family: React.PropTypes.string,
fvd: React.PropTypes.string,
onChange: React.PropTypes.func,
text: React.PropTypes.string
};
render () {
const style = Object.assign({}, this.props.style);
style.fontFamily = this.props.family;
utils.processFVD(style, this.props.fvd);
return (
{this.props.text}
);
}
}
================================================
FILE: lib/shared/screens/admin/shared/components/input-options/font/font.less
================================================
@import '~styles/colors.less';
.font {
color: @chromeTextColor;
font-size: 55px;
line-height: 77px;
height: 77px;
text-align: center;
max-width: 100%;
border: 0;
box-shadow: none;
padding: 0;
margin: 0;
padding-left: 7px;
}
================================================
FILE: lib/shared/screens/admin/shared/components/input-options/font/index.js
================================================
import Component from 'components/component';
import React, {PropTypes} from 'react';
import {dataConnect} from 'relate-js';
import FontPicker from './font-picker';
@dataConnect(
(state) => ({
fonts: state.fonts.data
}),
null,
() => ({
fragments: {
settings: {
_id: 1,
value: 1
}
},
variablesTypes: {
settings: {
ids: '[String]!'
}
},
initialVariables: {
settings: {
ids: ['fonts']
}
}
})
)
export default class FontPickerContainer extends Component {
static fragments = {
settings: {
_id: 1,
value: 1
}
};
static propTypes = {
value: PropTypes.string.isRequired,
onChange: PropTypes.string.isRequired,
fonts: PropTypes.object.isRequired
};
static defaultProps = {
fonts: {}
};
render () {
return (
);
}
}
================================================
FILE: lib/shared/screens/admin/shared/components/input-options/icon/icon-picker.jsx
================================================
import Component from 'components/component';
import React, {PropTypes} from 'react';
export default class IconPicker extends Component {
static propTypes = {
value: PropTypes.number.isRequired,
onChange: PropTypes.func.isRequired,
openSelector: PropTypes.func.isRequired,
closeSelector: PropTypes.func.isRequired
};
render () {
return (
{this.renderSelected()}
);
}
renderSelected () {
let result;
if (this.props.value && this.props.value.family) {
result = (
{this.props.value.content}
);
} else {
result = No icon selected
;
}
return result;
}
}
================================================
FILE: lib/shared/screens/admin/shared/components/input-options/icon/index.js
================================================
import Component from 'components/component';
import React, {PropTypes} from 'react';
import IconPicker from './icon-picker';
export default class IconPickerContainer extends Component {
static propTypes = {
value: PropTypes.string.isRequired,
onChange: PropTypes.func.isRequired
};
getInitState () {
return {
selector: false
};
}
openSelector () {
this.setState({
selector: true
});
}
closeSelector () {
this.setState({
selector: false
});
}
render () {
return (
);
}
}
================================================
FILE: lib/shared/screens/admin/shared/components/input-options/image/index.js
================================================
import * as mediaActions from 'actions/media';
import bind from 'decorators/bind';
import Component from 'components/component';
import React, {PropTypes} from 'react';
import {bindActionCreators} from 'redux';
import {dataConnect} from 'relate-js';
import ImagePicker from './picker';
@dataConnect(
null,
(dispatch) => bindActionCreators(mediaActions, dispatch),
(props) => {
const result = {
fragments: {},
variablesTypes: {
mediaItem: {
id: 'ID!'
}
},
initialVariables: {
mediaItem: {
id: props.value
}
}
};
if (props.value) {
result.fragments = ImagePicker.fragments;
}
return result;
}
)
export default class ImagePickerContainer extends Component {
static propTypes = {
value: PropTypes.string.isRequired,
onChange: PropTypes.func.isRequired,
type: PropTypes.string,
allowedType: PropTypes.string
};
static defaultProps = {
allowedType: 'image'
};
getInitState () {
return {
mounted: false,
calcWidth: 200,
selector: false
};
}
componentWillReceiveProps (nextProps) {
if (this.props.value !== nextProps.value && nextProps.value) {
this.props.relate.refresh(nextProps);
}
}
@bind
openSelector () {
this.setState({
selector: true
});
}
@bind
closeSelector () {
this.setState({
selector: false
});
}
@bind
onMount (width) {
this.setState({
mounted: true,
calcWidth: width
});
}
render () {
const {mediaItem, allowedType} = this.props;
return (
);
}
}
================================================
FILE: lib/shared/screens/admin/shared/components/input-options/image/picker.jsx
================================================
import bind from 'decorators/bind';
import Component from 'components/component';
import MediaItem from 'components/media-item-preview';
import MediaSelector from 'components/media-selector';
import React, {PropTypes} from 'react';
import styles from './picker.less';
export default class ImagePicker extends Component {
static fragments = MediaItem.fragments;
static propTypes = {
width: PropTypes.any,
height: PropTypes.number,
calcWidth: PropTypes.number,
onChange: PropTypes.func.isRequired,
value: PropTypes.string.isRequired,
selector: PropTypes.bool.isRequired,
openSelector: PropTypes.func.isRequired,
closeSelector: PropTypes.func.isRequired,
onMount: PropTypes.func.isRequired,
mounted: PropTypes.bool.isRequired,
mediaItem: PropTypes.object,
allowedType: PropTypes.string.isRequired
};
static defaultProps = {
width: '100%',
height: 135
};
componentDidMount () {
const dom = this.refs.imageHolder;
const rect = dom.getBoundingClientRect();
const width = Math.round(rect.right - rect.left);
this.props.onMount(width);
}
@bind
unselect () {
this.props.onChange(null);
}
render () {
const {openSelector} = this.props;
const style = {
width: this.props.width,
height: this.props.height
};
return (
{this.renderSelected()}
Choose Image
{this.renderUnselect()}
{this.renderMediaSelector()}
);
}
renderUnselect () {
const {value, width} = this.props;
if (value) {
return (
Unselect Image
);
}
}
renderSelected () {
if (this.props.mounted && this.props.value && this.props.mediaItem && this.props.mediaItem._id) {
return (
);
}
}
renderMediaSelector () {
const {selector, value, closeSelector, onChange, allowedType} = this.props;
if (selector) {
return (
);
}
}
}
================================================
FILE: lib/shared/screens/admin/shared/components/input-options/image/picker.less
================================================
@import '~styles/colors.less';
.picker {
display: inline-block;
}
.selected {
background-color: @chromeBordersColor;
position: relative;
overflow: hidden;
cursor: pointer;
width: 100%;
height: 100%;
img {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
}
.changeCover {
background-color: rgba(0, 0, 0, 0.6);
text-align: center;
padding: 50px 20px;
position: absolute;
top: 0; right: 0; bottom: 0; left: 0;
opacity: 0;
transition: all 0.3s ease-out;
color: @chromeTextColor;
}
.selected:hover .changeCover {
opacity: 1;
}
.unselect {
color: @chromeTextColor;
font-size: 12px;
text-transform: uppercase;
text-align: center;
padding: 10px 0;
cursor: pointer;
&:hover {
color: @alert;
}
}
================================================
FILE: lib/shared/screens/admin/shared/components/input-options/input/index.jsx
================================================
import bind from 'decorators/bind';
import classNames from 'classnames';
import cx from 'classnames';
import Animate from 'components/animate';
import Component from 'components/component';
import Spinner from 'components/spinner';
import React, {PropTypes} from 'react';
import styles from './index.less';
export default class Input extends Component {
static propTypes = {
value: PropTypes.string.isRequired,
onChange: PropTypes.func.isRequired,
className: PropTypes.string,
placeholder: PropTypes.string,
password: PropTypes.bool,
state: PropTypes.string,
disabled: PropTypes.bool,
focused: PropTypes.bool,
white: PropTypes.bool
};
componentDidMount () {
if (this.props.focused) {
this.refs.input.focus();
}
}
@bind
onChange (event) {
this.props.onChange(event.target.value);
}
render () {
const {disabled, className, state, password, white} = this.props;
return (
{this.renderState()}
);
}
renderState () {
const {state} = this.props;
if (state) {
if (state === 'valid') {
return (
);
} else if (state === 'invalid') {
return (
);
} else if (state === 'loading') {
return (
);
}
}
}
}
================================================
FILE: lib/shared/screens/admin/shared/components/input-options/input/index.less
================================================
@import '~styles/colors.less';
.input {
border-radius: 2px;
max-width: 480px;
position: relative;
:global input {
border: 0;
background: #2c2f34;
box-shadow: none;
width: 100%;
max-width: 480px;
display: block;
outline: none;
border: 1px solid @chromeBordersColor;
border-radius: 3px;
padding: 0px 12px;
font-size: 10px;
line-height: 38px;
color: @chromeTextColor;
transition: all 0.3s ease-out;
&:focus {
border-color: @primary;
}
&.valid {
border: 1px solid @success;
}
&.invalid {
border: 1px solid @alert;
}
}
}
.block {
max-width: none;
display: block;
:global input {
max-width: none;
}
}
.big {
:global input {
font-size: 17px;
}
}
.white {
:global input {
background: transparent;
border-color: @adminInputBorders;
color: @adminText;
&:focus {
border-color: @primary;
}
}
}
.disabled {
opacity: 0.5;
}
.withState {
:global input {
padding-right: 34px;
}
}
.state {
position: absolute;
top: 0;
right: 0;
bottom: 0;
width: 35px;
.stateContent {
position: absolute;
top: 50%;
left: 50%;
line-height: 21px;
height: 21px;
transform: translate(-50%, -50%);
font-size: 16px;
}
&.valid .stateContent {
color: @success;
}
&.invalid .stateContent {
color: @alert;
}
}
================================================
FILE: lib/shared/screens/admin/shared/components/input-options/menu/index.js
================================================
import forEach from 'lodash.foreach';
import Combobox from 'components/input-options/combobox';
import Component from 'components/component';
import React, {PropTypes} from 'react';
import {dataConnect} from 'relate-js';
@dataConnect(
() => ({
fragments: {
menus: {
_id: 1,
title: 1
}
}
})
)
export default class MenuPickerContainer extends Component {
static propTypes = {
value: PropTypes.string.isRequired,
onChange: PropTypes.string.isRequired,
menus: PropTypes.array.isRequired
};
static defaultProps = {
menus: []
};
render () {
const labels = [];
const values = [];
forEach(this.props.menus, (menu) => {
labels.push(menu.title);
values.push(menu._id);
});
return (
);
}
}
================================================
FILE: lib/shared/screens/admin/shared/components/input-options/number/index.jsx
================================================
import cx from 'classnames';
import Component from 'components/component';
import React, {PropTypes} from 'react';
import styles from './index.less';
export default class NumberInput extends Component {
static propTypes = {
value: PropTypes.number.isRequired,
onChange: PropTypes.func.isRequired,
allowed: PropTypes.array,
min: PropTypes.number,
max: PropTypes.number,
inactive: PropTypes.bool,
className: PropTypes.string,
arrows: PropTypes.bool,
white: PropTypes.bool,
small: PropTypes.bool
};
static defaultProps = {
min: 0,
max: false,
allowed: [],
inactive: false,
arrows: true
};
getInitState () {
return {
focused: false
};
}
limitValue (value) {
let result = value;
if (this.props.min !== false) {
if (value < this.props.min) {
result = this.props.min;
}
}
if (this.props.max !== false) {
if (value > this.props.max) {
result = this.props.max;
}
}
return result;
}
getUnitAndValue (str, defaultUnit = this.props.allowed.length > 0 && this.props.allowed[0] || '') {
let result;
if (str === 'auto') {
result = {
value: 'auto',
unit: ''
};
} else {
let value = str !== '' && parseFloat(str, 10);
value = (value === false || isNaN(value)) ? this.limitValue(0) : value;
let unit = defaultUnit;
const valueLength = (value.toString()).length;
if (valueLength < str.length && this.props.allowed.indexOf(str.substr(valueLength)) > -1) {
unit = str.substr(valueLength);
}
result = {
value,
unit
};
}
return result;
}
onInput (event) {
const string = event.target.value.replace(',', '.').toLowerCase();
if (string === 'auto' && this.props.allowed.indexOf('auto') !== 0) {
this.props.onChange(string);
} else {
const unitValue = this.getUnitAndValue(string, this.state.unitValue.unit);
this.props.onChange(this.limitValue(unitValue.value) + unitValue.unit);
}
this.setState({
value: string
});
}
up (event) {
event.preventDefault();
if (this.props.value === 'auto') {
this.props.onChange(this.limitValue(1) + this.props.allowed[0]);
} else {
const unitValue = this.getUnitAndValue(this.props.value);
this.props.onChange(this.limitValue(unitValue.value + 1) + unitValue.unit);
}
}
down (event) {
event.preventDefault();
if (this.props.value === 'auto') {
this.props.onChange(this.limitValue(-1) + this.props.allowed[0]);
} else {
const unitValue = this.getUnitAndValue(this.props.value);
this.props.onChange(this.limitValue(unitValue.value - 1) + unitValue.unit);
}
}
onFocus () {
this.setState({
focused: true,
value: this.props.inactive ? '--' : this.props.value,
unitValue: this.getUnitAndValue(this.props.value)
});
}
onBlur () {
this.setState({
focused: false,
unitValue: null
});
}
render () {
const {className, white, small} = this.props;
const {focused} = this.state;
const value = this.props.inactive ? '--' : this.props.value;
return (
{this.renderArrows()}
);
}
renderArrows () {
const {arrows} = this.props;
if (arrows) {
return (
);
}
}
}
================================================
FILE: lib/shared/screens/admin/shared/components/input-options/number/index.less
================================================
@import '~styles/colors.less';
.root {
width: 100%;
max-width: 150px;
height: 38px;
display: block;
position: relative;
outline: none;
background-color: @chromeBackgroundDarkerColor;
border: 1px solid @chromeBordersColor;
border-radius: 3px;
overflow: hidden;
transition: all 0.3s ease-out;
}
.focused {
border: 1px solid @primary;
}
.small {
width: 80px;
}
.input {
display: inline-block;
vertical-align: top;
border: 0;
outline: 0;
background: transparent;
box-shadow: none;
line-height: 36px;
padding: 0 12px;
padding-right: 40px;
width: 100%;
font-size: 10px;
color: @chromeTextColor;
}
.arrows {
display: inline-block;
vertical-align: top;
position: absolute;
width: 20px;
top: 0;
right: 0;
bottom: 0;
}
.arrow {
display: inline-block;
color: @chromeTextSubColor;
position: absolute;
top: 0;
left: 0;
right: 0;
height: 19px;
width: 20px;
text-align: center;
font-size: 12px;
:global i {
line-height: 18px;
font-size: 7px;
color: @chromeTextSubColor;
}
&:last-child {
top: auto;
bottom: 0;
}
&:hover {
background-color: @chromeBackgroundActive;
}
}
.white {
border-color: @adminInputBorders;
background-color: transparent;
.input {
color: @adminText;
}
.arrow {
color: @adminText;
&:hover {
background-color: transparent;
}
}
}
================================================
FILE: lib/shared/screens/admin/shared/components/input-options/optional/index.jsx
================================================
import Component from 'components/component';
import React from 'react';
import styles from './index.less';
export default class Optional extends Component {
static propTypes = {
value: React.PropTypes.bool.isRequired,
onChange: React.PropTypes.func.isRequired,
label: React.PropTypes.string.isRequired
};
toggle (event) {
event.preventDefault();
this.props.onChange(!this.props.value);
}
render () {
return (
{this.props.label}
{this.props.value && }
);
}
}
================================================
FILE: lib/shared/screens/admin/shared/components/input-options/optional/index.less
================================================
@import '~styles/colors.less';
@size: 15px;
.root {
position: relative;
cursor: pointer;
padding: 0 3px;
margin-bottom: 8px;
&:hover {
.label {
color: @chromeTextColor;
}
.box {
border-color: @primary;
}
}
}
.box, .label {
vertical-align: top;
line-height: 17px;
transition: all 0.3s ease-out;
}
.label {
text-transform: uppercase;
font-size: 10px;
color: @chromeTextColor;
line-height: 16px;
padding-right: @size + 5px;
&.white {
color: @adminText;
}
}
.box {
position: absolute;
right: 3px;
top: 1px;
display: inline-block;
width: @size;
height: @size;
text-align: center;
border: 1px solid @chromeTextSubColor;
border-radius: 3px;
:global i {
font-size: 9px;
color: #ffffff;
line-height: 14px;
}
}
================================================
FILE: lib/shared/screens/admin/shared/components/input-options/page/index.js
================================================
import forEach from 'lodash.foreach';
import Combobox from 'components/input-options/combobox';
import Component from 'components/component';
import React, {PropTypes} from 'react';
import {dataConnect} from 'relate-js';
@dataConnect(
() => ({
fragments: {
pages: {
_id: 1,
title: 1
}
}
})
)
export default class PagePickerContainer extends Component {
static propTypes = {
value: PropTypes.string.isRequired,
onChange: PropTypes.func.isRequired,
pages: PropTypes.array.isRequired,
fetchData: PropTypes.func.isRequired
};
static defaultProps = {
pages: []
};
componentDidMount () {
this.props.fetchData({
fragments: this.constructor.fragments
});
}
render () {
const labels = [];
const values = [];
forEach(this.props.pages, (page) => {
labels.push(page.title);
values.push(page._id);
});
return (
);
}
}
================================================
FILE: lib/shared/screens/admin/shared/components/input-options/rich-text/index.jsx
================================================
import classNames from 'classnames';
import Component from 'components/component';
import Editor from 'components/medium-editor';
import React, {PropTypes} from 'react';
export default class HtmlArea extends Component {
static propTypes = {
value: PropTypes.string.isRequired,
onChange: PropTypes.func.isRequired,
className: PropTypes.string
};
render () {
return (
);
}
}
================================================
FILE: lib/shared/screens/admin/shared/components/input-options/schema/index.js
================================================
import forEach from 'lodash.foreach';
import Combobox from 'components/input-options/combobox';
import Component from 'components/component';
import React, {PropTypes} from 'react';
import {dataConnect} from 'relate-js';
@dataConnect(
() => ({
fragments: {
schemas: {
_id: 1,
title: 1
}
}
})
)
export default class SchemaPickerContainer extends Component {
static propTypes = {
value: PropTypes.string.isRequired,
onChange: PropTypes.string.isRequired,
schemas: PropTypes.array.isRequired
};
render () {
const labels = [];
const values = [];
forEach(this.props.schemas, (schema) => {
labels.push(schema.title);
values.push(schema._id);
});
return (
);
}
}
================================================
FILE: lib/shared/screens/admin/shared/components/input-options/section/index.jsx
================================================
import cx from 'classnames';
import Component from 'components/component';
import React from 'react';
import styles from './index.less';
export default class Section extends Component {
static propTypes = {
value: React.PropTypes.bool.isRequired,
label: React.PropTypes.string.isRequired,
onChange: React.PropTypes.func.isRequired
};
toggle (event) {
event.preventDefault();
this.props.onChange(!this.props.value);
}
render () {
const {value, label} = this.props;
return (
{label}
);
}
}
================================================
FILE: lib/shared/screens/admin/shared/components/input-options/section/index.less
================================================
@import '~styles/colors.less';
.root {
margin: 0 -13px;
cursor: pointer;
padding: 0 10px;
margin-top: -1px;
border-top: 1px solid @chromeBordersColor;
border-bottom: 1px solid @chromeBordersColor;
:global {
i, span {
display: inline-block;
vertical-align: top;
line-height: 50px;
color: @chromeTextColor;
}
i {
font-size: 10px;
padding-right: 10px;
}
span {
font-size: 11px;
text-transform: uppercase;
}
}
&:hover {
background-color: @chromeBackgroundActive;
:global {
i, span {
color: @chromeTextColorHighlight;
}
}
}
}
================================================
FILE: lib/shared/screens/admin/shared/components/input-options/shadow-position/index.jsx
================================================
import cx from 'classnames';
import Component from 'components/component';
import React, {PropTypes} from 'react';
import styles from './index.less';
export default class ShadowPosition extends Component {
static propTypes = {
value: PropTypes.string.isRequired,
onChange: PropTypes.func.isRequired
};
init () {
this.onOutset = this.onClick.bind(this, 'outset');
this.onInset = this.onClick.bind(this, 'inset');
}
onClick (to) {
this.props.onChange(to);
}
render () {
const {value} = this.props;
return (
);
}
}
================================================
FILE: lib/shared/screens/admin/shared/components/input-options/shadow-position/index.less
================================================
@import '~styles/colors.less';
.root {
padding-top: 5px;
}
.outset, .inset {
display: inline-block;
width: 24px;
height: 24px;
background-color: #ffffff;
margin-right: 9px;
border-radius: 4px;
}
.outset {
-webkit-box-shadow: 0px 0px 2px 1px rgba(0,0,0,0.3);
-moz-box-shadow: 0px 0px 2px 1px rgba(0,0,0,0.3);
box-shadow: 0px 0px 2px 1px rgba(0,0,0,0.3);
&.active {
-webkit-box-shadow: 0px 0px 3px 1px @primary;
-moz-box-shadow: 0px 0px 3px 1px @primary;
box-shadow: 0px 0px 3px 1px @primary;
}
}
.inset {
-webkit-box-shadow: inset 0px 0px 2px 1px rgba(0,0,0,0.3);
-moz-box-shadow: inset 0px 0px 2px 1px rgba(0,0,0,0.3);
box-shadow: inset 0px 0px 2px 1px rgba(0,0,0,0.3);
&.active {
-webkit-box-shadow: inset 0px 0px 3px 1px @primary;
-moz-box-shadow: inset 0px 0px 3px 1px @primary;
box-shadow: inset 0px 0px 3px 1px @primary;
}
}
================================================
FILE: lib/shared/screens/admin/shared/components/input-options/sorts/edit.jsx
================================================
import find from 'lodash.find';
import forEach from 'lodash.foreach';
import Combobox from 'components/input-options/combobox';
import Component from 'components/component';
import React, {PropTypes} from 'react';
export default class Edit extends Component {
static propTypes = {
schema: PropTypes.object,
sort: PropTypes.object.isRequired,
new: PropTypes.bool,
onPropertyChange: PropTypes.func.isRequired,
onOrderChange: PropTypes.func.isRequired,
cancelEdit: PropTypes.func.isRequired,
submitEdit: PropTypes.func.isRequired,
schemaProperties: PropTypes.array.isRequired
};
render () {
const {sort, schemaProperties} = this.props;
const labels = [];
const values = [];
forEach(schemaProperties, (property) => {
labels.push(property.title);
values.push(property.id);
});
return (
cancel
{this.props.new ? 'Create new sort' : 'Ok'}
{this.renderOptions()}
);
}
renderOptions () {
let result;
const {sort, schemaProperties} = this.props;
const property = find(schemaProperties, 'id', sort.prop);
if (property) {
let labels = [];
switch (property.type) {
case 'Date':
labels = ['Newest to oldest', 'Oldest to newest'];
break;
case 'Boolean':
labels = ['Trues first', 'Falses first'];
break;
default:
labels = ['Alphabetical order [A-Z]', 'Inverse alphabetical order [Z-A]'];
}
result = (
);
} else {
result = (
Invalid sort
);
}
return result;
}
}
================================================
FILE: lib/shared/screens/admin/shared/components/input-options/sorts/index.js
================================================
import * as elementsActions from 'actions/elements';
import cloneDeep from 'lodash.clonedeep';
import forEach from 'lodash.foreach';
import staticProperties from 'helpers/schema-static-properties';
import Component from 'components/component';
import React, {PropTypes} from 'react';
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
import {buildQueryAndVariables} from 'relax-fragments';
import Sorts from './sorts';
@connect(
(state) => ({
elements: state.elements,
selectedId: state.pageBuilder.selectedId,
selectedElement: state.pageBuilder.selectedElement
}),
(dispatch) => bindActionCreators(elementsActions, dispatch)
)
export default class SortsContainer extends Component {
static fragments = Sorts.fragments;
static propTypes = {
value: PropTypes.array.isRequired,
onChange: PropTypes.func.isRequired,
elements: PropTypes.object.isRequired,
selectedId: PropTypes.string,
selectedElement: PropTypes.object
};
static defaultProps = {
value: []
};
getInitState () {
this.fetchData(this.props);
return {
newOpened: false
};
}
componentWillReceiveProps (nextProps) {
if (this.getSelectedSchemaId(nextProps) !== this.getSelectedSchemaId(this.props)) {
this.fetchData(nextProps);
}
}
getSelectedSchemaId (props) {
const {selectedElement} = props;
return selectedElement && selectedElement.props && selectedElement.props.schemaId;
}
fetchData (props) {
const {selectedId} = props;
const schemaId = this.getSelectedSchemaId(props);
if (schemaId) {
props.getElementData(selectedId, buildQueryAndVariables(
this.constructor.fragments,
{
schema: {
_id: {
value: schemaId,
type: 'ID!'
}
}
}
));
}
}
openNew () {
this.setState({
newOpened: true,
editingSort: {
prop: 'publishedDate',
type: 'Date',
options: {}
}
});
}
onPropertyChange (prop) {
this.setState({
editingSort: Object.assign({}, this.state.editingSort, {
prop
})
});
}
onOrderChange (order) {
this.setState({
editingSort: Object.assign({}, this.state.editingSort, {
order
})
});
}
cancelEdit () {
this.setState({
newOpened: false,
editOpened: false,
editIndex: null
});
}
submitEdit () {
let newSorts = this.props.value;
if (this.state.newOpened) {
newSorts = [...newSorts, this.state.editingSort];
} else if (this.state.editOpened) {
newSorts = [...newSorts];
newSorts[this.state.editIndex] = this.state.editingSort;
}
this.props.onChange(newSorts);
this.setState({
newOpened: false,
editOpened: false,
editIndex: null
});
}
selectSort (index) {
if (this.state.editOpened && this.state.editIndex === index) {
this.setState({
editOpened: false,
editIndex: null
});
} else {
this.setState({
editOpened: true,
editIndex: index,
editingSort: cloneDeep(this.props.value[index])
});
}
}
removeSort (index) {
const newSorts = [...this.props.value];
newSorts.splice(index, 1);
this.props.onChange(newSorts);
}
render () {
const {selectedId, elements} = this.props;
const elementData = elements[selectedId];
const schema = elementData && elementData.schema || null;
const schemaProperties = schema && schema.properties || [];
const properties = [...staticProperties];
forEach(schemaProperties, (customProperty) => {
properties.push(Object.assign({}, customProperty, {
id: `properties#${customProperty.id}`
}));
});
return (
);
}
}
================================================
FILE: lib/shared/screens/admin/shared/components/input-options/sorts/sort.jsx
================================================
import find from 'lodash.find';
import Balloon from 'components/balloon';
import Component from 'components/component';
import React, {PropTypes} from 'react';
import Edit from './edit';
export default class Sort extends Component {
static propTypes = {
editing: PropTypes.bool.isRequired,
sort: PropTypes.object.isRequired,
schemaProperties: PropTypes.array.isRequired,
index: PropTypes.number.isRequired,
selectSort: PropTypes.func.isRequired,
removeSort: PropTypes.func.isRequired,
new: PropTypes.bool
};
onClick () {
if (!this.props.new) {
this.props.selectSort(this.props.index);
}
}
onRemove (event) {
event.preventDefault();
event.stopPropagation();
this.props.removeSort(this.props.index);
}
render () {
return (
{
this.ref = ref;
!this.state.ready && this.setState({ready: true});
}}
>
{this.renderContent()}
{
!this.props.new &&
delete
}
{this.renderEditing()}
);
}
renderContent () {
let result;
const {sort, schemaProperties} = this.props;
const property = find(schemaProperties, 'id', sort.prop);
if (property) {
switch (property.type) {
case 'Date':
if (sort.order === 'ASC') {
result = (
{property.title}
newest
to
oldest
);
} else {
result = (
{property.title}
oldest
to
newest
);
}
break;
case 'Boolean':
if (sort.order === 'ASC') {
result = (
{property.title}
first
);
} else {
result = (
{property.title}
last
);
}
break;
default:
if (sort.order === 'ASC') {
result = (
{property.title}
alphabetical
order
[A-Z]
);
} else {
result = (
{property.title}
inverse alphabetical
order
[Z-A]
);
}
}
}
return result;
}
renderEditing () {
if (this.props.editing && this.state.ready) {
return (
);
}
}
}
================================================
FILE: lib/shared/screens/admin/shared/components/input-options/sorts/sorts.jsx
================================================
import Component from 'components/component';
import React, {PropTypes} from 'react';
import Sort from './sort';
export default class Sorts extends Component {
static fragments = {
schema: {
_id: 1,
title: 1,
properties: 1
}
};
static propTypes = {
value: PropTypes.string.isRequired,
openNew: PropTypes.func.isRequired,
newOpened: PropTypes.bool.isRequired,
schema: PropTypes.object.isRequired,
editingSort: PropTypes.object,
editOpened: PropTypes.bool,
editIndex: PropTypes.number
};
render () {
return (
{this.props.value.map(this.renderSort, this)}
{this.renderNew()}
Add new sort condition
);
}
renderSort (sort, index) {
const editing = this.props.editOpened && this.props.editIndex === index;
return (
);
}
renderNew () {
if (this.props.newOpened) {
return (
);
}
}
}
================================================
FILE: lib/shared/screens/admin/shared/components/input-options/spacing/index.jsx
================================================
import cx from 'classnames';
import Component from 'components/component';
import NumberInput from 'components/input-options/number';
import React from 'react';
import styles from './index.less';
export default class SpacingPicker extends Component {
static propTypes = {
value: React.PropTypes.string.isRequired,
onChange: React.PropTypes.func.isRequired,
type: React.PropTypes.string.isRequired
};
getInitState () {
return {
selected: 'center',
values: this.parseValue(this.props.value)
};
}
componentWillReceiveProps (nextProps) {
this.setState({
values: this.parseValue(nextProps.value)
});
}
onInputChange (value) {
if (this.state.selected === 'center') {
this.state.values.top = value;
this.state.values.right = value;
this.state.values.bottom = value;
this.state.values.left = value;
} else {
this.state.values[this.state.selected] = value;
}
this.props.onChange(this.getValuesString(this.state.values));
}
getValuesString (values) {
return `${values.top || '0'}px ${values.right || '0'}px ${values.bottom || '0'}px ${values.left || '0'}px`;
}
parseValue (value) {
const values = value.split(' ');
const result = {
top: 0,
left: 0,
right: 0,
bottom: 0,
equal: false
};
if (values.length === 1) {
const parsedValue = parseInt(values[0], 10);
result.top = parsedValue;
result.bottom = parsedValue;
result.left = parsedValue;
result.right = parsedValue;
} else if (values.length === 2) {
result.top = parseInt(values[0], 10);
result.right = parseInt(values[1], 10);
result.bottom = parseInt(values[0], 10);
result.left = parseInt(values[1], 10);
} else if (values.length === 4) {
result.top = parseInt(values[0], 10);
result.right = parseInt(values[1], 10);
result.bottom = parseInt(values[2], 10);
result.left = parseInt(values[3], 10);
}
if (result.top === result.right && result.top === result.bottom && result.top === result.left) {
result.equal = true;
} else {
result.equal = false;
}
return result;
}
changeSelected (selected, event) {
event.preventDefault();
this.setState({
selected
});
}
render () {
const values = this.state.values;
let value = 0;
let inactive = false;
if (this.state.selected !== 'center') {
value = values[this.state.selected];
} else {
inactive = !values.equal;
value =
values.equal ?
values.top :
Math.round((values.top + values.right + values.bottom + values.left) / 4);
}
return (
{this.renderToggleButton('top', 'arrows-1_minimal-up', !values.equal)}
{this.renderToggleButton('left', 'arrows-1_minimal-left', !values.equal)}
{this.renderToggleButton('center', 'ui-2_link-68', values.equal)}
{this.renderToggleButton('right', 'arrows-1_minimal-right', !values.equal)}
{this.renderToggleButton('bottom', 'arrows-1_minimal-down', !values.equal)}
);
}
renderToggleButton (pos, icon, active) {
const onClick = this.changeSelected.bind(this, pos);
return (
);
}
}
================================================
FILE: lib/shared/screens/admin/shared/components/input-options/spacing/index.less
================================================
@import '~styles/colors.less';
@btnsSize: 25px;
@btnsSpacing: 3px;
@total: @btnsSize * 3 + @btnsSpacing * 2;
.root {
display: table;
width: 100%;
table-layout: fixed;
margin-top: 10px;
}
// Parts
.toggles, .inputs {
display: table-cell;
vertical-align: middle;
}
.toggles {
position: relative;
height: @total;
width: @total;
}
.inputs {
padding-left: 25px;
padding-bottom: 13px;
.subLabel {
font-size: 9px;
color: #616363;
text-transform: uppercase;
margin-bottom: 5px;
}
}
// Toggle buttons
.toggle {
position: absolute;
width: @btnsSize;
height: @btnsSize;
border: 1px solid @chromeBordersColor;
text-align: center;
cursor: pointer;
:global i {
color: #cccccc;
font-size: 9px;
line-height: 23px;
position: relative;
}
@center: @total / 2 - @btnsSize / 2;
&.top {
left: @center;
top: 0;
}
&.left {
left: 0;
top: @center;
}
&.center {
left: @center;
top: @center;
}
&.right {
right: 0;
top: @center;
}
&.bottom {
left: @center;
bottom: 0;
}
&:before {
content: " ";
position: absolute;
background-color: #cccccc;
}
&.top:before, &.bottom:before {
height: 1px;
width: 9px;
left: @btnsSize / 2 - 6px;
}
&.left:before, &.right:before {
height: 9px;
width: 1px;
top: @btnsSize / 2 - 6px;
}
&:hover {
background-color: #3f4249;
:global i {
color: #ffffff;
}
&:before {
background-color: #ffffff;
}
}
&.selected {
border-color: @primary;
background-color: #3f4249;
:global i {
color: #ffffff;
}
&:before {
background-color: #ffffff;
}
}
&.active {
:global i {
color: @primary;
}
&:before {
background-color: @primary;
}
}
}
// Type
.padding {
.top {
&:before {
top: 7px;
}
:global i {
top: 3px;
}
}
.bottom {
&:before {
bottom: 7px;
}
:global i {
top: -3px;
}
}
.left {
&:before {
left: 7px;
}
:global i {
left: 3px;
}
}
.right {
&:before {
right: 7px;
}
:global i {
left: -3px;
}
}
}
.margin {
.top {
&:before {
bottom: 7px;
}
:global i {
top: -3px;
}
}
.bottom {
&:before {
top: 7px;
}
:global i {
top: 3px;
}
}
.left {
&:before {
right: 7px;
}
:global i {
left: -3px;
}
}
.right {
&:before {
left: 7px;
}
:global i {
left: 3px;
}
}
}
================================================
FILE: lib/shared/screens/admin/shared/components/input-options/text-shadow/edit.jsx
================================================
import Component from 'components/component';
import React, {PropTypes} from 'react';
export default class Edit extends Component {
static propTypes = {
shadow: PropTypes.object.isRequired,
changeShadow: PropTypes.func.isRequired,
OptionsList: PropTypes.object.isRequired
};
static options = [
{
type: 'Columns',
options: [
{
label: 'Color',
type: 'Color',
id: 'color'
},
{
label: 'Blur',
type: 'Pixels',
id: 'blur'
}
]
},
{
type: 'Columns',
options: [
{
label: 'X',
type: 'Pixels',
id: 'x'
},
{
label: 'Y',
type: 'Pixels',
id: 'y'
}
]
}
];
render () {
const {shadow, OptionsList, changeShadow} = this.props;
return (
);
}
}
================================================
FILE: lib/shared/screens/admin/shared/components/input-options/text-shadow/index.jsx
================================================
import cloneDeep from 'lodash.clonedeep';
import Component from 'components/component';
import React, {PropTypes} from 'react';
import styles from './index.less';
import Shadow from './shadow';
export default class TextShadow extends Component {
static propTypes = {
value: PropTypes.array.isRequired,
onChange: PropTypes.func.isRequired
};
static defaultProps = {
value: []
};
getInitState () {
return {
editingShadow: false
};
}
addNewClick () {
this.props.onChange([...this.props.value, {
color: '#000000',
blur: '2px',
x: '2px',
y: '2px'
}]);
this.setState({
editingShadow: this.props.value.length
});
}
changeShadow (key, value) {
if (this.state.editingShadow !== false) {
const newValue = cloneDeep(this.props.value);
newValue[this.state.editingShadow][key] = value;
this.props.onChange(newValue);
}
}
selectShadow (index) {
if (this.state.editingShadow === index) {
this.setState({
editingShadow: false
});
} else {
this.setState({
editingShadow: index
});
}
}
removeShadow (index) {
const newValue = cloneDeep(this.props.value);
newValue.splice(index, 1);
this.props.onChange(newValue);
}
render () {
return (
{this.props.value.map(this.renderEntry, this)}
Add new shadow
);
}
renderEntry (shadow, index) {
return (
);
}
}
================================================
FILE: lib/shared/screens/admin/shared/components/input-options/text-shadow/index.less
================================================
@import '~styles/colors.less';
.addButton {
font-size: 10px;
color: #dbdbdb;
cursor: pointer;
line-height: 20px;
text-align: center;
text-transform: uppercase;
margin-top: 15px;
&:hover {
color: @primary;
}
}
================================================
FILE: lib/shared/screens/admin/shared/components/input-options/text-shadow/shadow.jsx
================================================
import Balloon from 'components/balloon';
import Component from 'components/component';
import React, {PropTypes} from 'react';
import {getColor} from 'helpers/colors';
import styles from './shadow.less';
import Edit from './edit';
export default class Shadow extends Component {
static propTypes = {
shadow: PropTypes.object.isRequired,
editing: PropTypes.bool.isRequired,
new: PropTypes.bool.isRequired,
selectShadow: PropTypes.func.isRequired,
removeShadow: PropTypes.func.isRequired,
index: PropTypes.number.isRequired
};
onClick () {
this.props.selectShadow(this.props.index);
}
onRemove (event) {
event.preventDefault();
event.stopPropagation();
this.props.removeShadow(this.props.index);
}
render () {
const {shadow} = this.props;
return (
{
this.ref = ref;
!this.state.ready && this.setState({ready: true});
}}
>
{`${getColor(shadow.color).label}, ${shadow.x} ${shadow.y}, ${shadow.blur}`}
{this.renderEditing()}
);
}
renderEditing () {
if (this.props.editing && this.state.ready) {
return (
);
}
}
}
================================================
FILE: lib/shared/screens/admin/shared/components/input-options/text-shadow/shadow.less
================================================
@import '~styles/colors.less';
.root {
position: relative;
}
.content {
position: relative;
border: 1px solid @chromeBordersColor;
padding: 12px;
font-size: 10px;
line-height: 14px;
color: @chromeTextColor;
border-radius: 4px;
cursor: pointer;
margin-bottom: 8px;
&:hover {
border-color: @primary;
.removeButton {
visibility: visible;
}
}
}
.removeButton {
position: absolute;
right: 1px; top: 1px; bottom: 1px;
width: 30px;
text-align: center;
visibility: hidden;
background: -moz-linear-gradient(left, rgba(51,54,59,0) 0%, rgba(51,54,59,1) 23%, rgba(51,54,59,1) 100%);
background: -webkit-linear-gradient(left, rgba(51,54,59,0) 0%,rgba(51,54,59,1) 23%,rgba(51,54,59,1) 100%);
background: linear-gradient(to right, rgba(51,54,59,0) 0%,rgba(51,54,59,1) 23%,rgba(51,54,59,1) 100%);
:global i {
color: @chromeTextSubColor;
line-height: 36px;
font-size: 11px;
}
&:hover {
:global i {
color: #ffffff;
}
}
}
================================================
FILE: lib/shared/screens/admin/shared/components/list-header/index.jsx
================================================
import Component from 'components/component';
import React, {PropTypes} from 'react';
import styles from './index.less';
export default class ListHeader extends Component {
static propTypes = {
onBack: PropTypes.func.isRequired,
title: PropTypes.string.isRequired,
newIcon: PropTypes.string,
onNew: PropTypes.func,
children: PropTypes.node
};
render () {
const {onBack, title} = this.props;
return (
{title}
{this.renderNewButton()}
{this.props.children}
);
}
renderNewButton () {
const {newIcon, onNew} = this.props;
if (newIcon && onNew) {
return (
);
}
}
}
================================================
FILE: lib/shared/screens/admin/shared/components/list-header/index.less
================================================
@import '~styles/colors.less';
.header {
position: relative;
height: 70px;
border-bottom: 1px solid @adminBorders;
}
.backButton {
position: absolute;
top: 0;
left: 0;
padding: 0 7px;
:global {
i, span {
display: inline-block;
vertical-align: top;
line-height: 70px;
}
span {
text-transform: uppercase;
font-size: 12px;
}
i {
font-size: 10px;
margin-right: 5px;
}
}
}
.newButton {
position: absolute;
top: 0;
right: 0;
padding: 0 10px;
:global {
i {
color: @adminText;
line-height: 70px;
font-size: 22px;
}
}
&:hover :global i {
color: @primary;
}
}
.title {
padding: 0 60px;
text-align: center;
line-height: 69px;
font-size: 22px;
color: @adminTextHighlight;
}
================================================
FILE: lib/shared/screens/admin/shared/components/list-search-sort/index.jsx
================================================
import bind from 'decorators/bind';
import cx from 'classnames';
import debounce from 'lodash.debounce';
import find from 'lodash.find';
import Balloon from 'components/balloon';
import Component from 'components/component';
import React, {PropTypes} from 'react';
import {Link} from 'react-router';
import {pushState} from 'redux-router';
import styles from './index.less';
export default class ListSearchSort extends Component {
static propTypes = {
sorts: PropTypes.array.isRequired,
sort: PropTypes.string.isRequired,
search: PropTypes.string,
order: PropTypes.string.isRequired,
location: PropTypes.object.isRequired
};
static contextTypes = {
store: PropTypes.object.isRequired
};
getInitState () {
this.searchDebounce = debounce(::this.changeSearch, 300);
return {
opened: false,
focused: false
};
}
toggleSorts () {
this.setState({
opened: !this.state.opened
});
}
changeSearch () {
const {location} = this.props;
const query = Object.assign({}, location.query, {
s: this.state.search
});
this.context.store.dispatch(pushState(null, location.pathname, query));
}
@bind
onFocus () {
this.setState({
focused: true,
search: this.props.search
});
}
@bind
onBlur () {
if (this.state.search !== this.props.search) {
this.changeSearch();
}
this.setState({
focused: false
});
}
@bind
updateSearch (event) {
this.setState({
search: event.target.value
});
this.searchDebounce();
}
render () {
const {focused} = this.state;
const {sort, order, sorts} = this.props;
const selected = find(sorts, (obj) => obj.sort === sort && obj.order === order);
return (
);
}
renderSorts () {
if (this.state.opened) {
const {sorts} = this.props;
return (
{sorts.map(this.renderSort, this)}
);
}
}
renderSort (sort, key) {
const {location} = this.props;
const active = this.props.sort === sort.sort && this.props.order === sort.order;
const query = Object.assign({}, location.query, {
sort: sort.sort,
order: sort.order
});
return (
{sort.label}
);
}
}
================================================
FILE: lib/shared/screens/admin/shared/components/list-search-sort/index.less
================================================
@import '~styles/colors.less';
.root {
background-color: #F7F7F7;
height: 40px;
border-bottom: 1px solid @adminBorders;
padding: 0 10px;
}
.searchLabel {
display: inline-block;
vertical-align: top;
:global i {
font-size: 16px;
color: @adminTextSub;
display: inline-block;
vertical-align: top;
line-height: 39px;
}
}
.search {
border: 0;
outline: 0;
color: @adminTextHighlight;
font-size: 12px;
font-weight: 300;
display: inline-block;
vertical-align: top;
background-color: transparent;
line-height: 40px;
padding: 0px 7px;
&::-webkit-input-placeholder {
color: #b4b4b4;
}
&:-moz-placeholder {
color: #b4b4b4;
}
&::-moz-placeholder {
color: #b4b4b4;
}
&:-ms-input-placeholder {
color: #b4b4b4;
}
&::-ms-input-placeholder {
color: #b4b4b4;
}
&:placeholder-shown {
color: #b4b4b4;
}
}
.sort {
float: right;
display: inline-block;
vertical-align: top;
cursor: pointer;
:global {
i, span {
display: inline-block;
vertical-align: top;
line-height: 40px;
color: #b4b4b4;
}
i {
font-size: 7px;
margin-left: 6px;
}
span {
font-size: 12px;
font-weight: 300;
}
}
}
.sortOption {
display: block;
width: 100%;
color: @adminText;
line-height: 30px;
font-size: 12px;
text-align: left;
padding: 0 15px;
text-decoration: none;
&:hover {
background-color: @adminBorders;
}
&.active {
background-color: @primary;
color: #ffffff;
}
}
================================================
FILE: lib/shared/screens/admin/shared/components/media-item-preview/index.jsx
================================================
import Component from 'components/component';
import Image from 'components/image';
import React, {PropTypes} from 'react';
import {getMediaType} from 'helpers/mime-types';
import styles from './index.less';
export default class MediaItemPreview extends Component {
static fragments = {
mediaItem: {
_id: 1,
type: 1,
thumbnail: 1,
url: 1
}
};
static propTypes = {
mediaItem: PropTypes.object.isRequired,
useThumbnail: PropTypes.bool,
width: PropTypes.number,
height: PropTypes.number
};
static defaultProps = {
useThumbnail: false,
width: 100,
height: 100,
mediaItem: {}
};
render () {
const {mediaItem} = this.props;
const type = getMediaType(mediaItem.type);
let result;
if (type === 'favicon') {
result = this.renderFaviconType();
} else if (type === 'image') {
result = this.renderImageType();
} else if (type === 'video') {
result = this.renderVideoType();
} else if (type === 'audio') {
result = this.renderAudioType();
}
return (
{result}
);
}
renderImageType () {
const {mediaItem, width, height, useThumbnail} = this.props;
let result;
if (useThumbnail) {
result = (
);
} else {
result = (
);
}
return result;
}
renderFaviconType () {
const {mediaItem} = this.props;
return (
);
}
renderVideoType () {
return (
);
}
renderAudioType () {
return (
);
}
}
================================================
FILE: lib/shared/screens/admin/shared/components/media-item-preview/index.less
================================================
@import '~styles/colors.less';
.root {
width: 100%;
height: 100%;
text-align: center;
overflow: hidden;
position: relative;
:global {
img, i {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
i {
font-size: 36px;
color: @adminText;
}
}
}
.cover {
min-width: 100%;
min-height: 100%;
}
.limit {
max-width: 100%;
max-height: 100%;
}
================================================
FILE: lib/shared/screens/admin/shared/components/media-selector/entry.jsx
================================================
import bind from 'decorators/bind';
import cx from 'classnames';
import moment from 'moment';
import Component from 'components/component';
import MediaItemPreview from 'components/media-item-preview';
import React, {PropTypes} from 'react';
import {mergeFragments} from 'relate-js';
import styles from './entry.less';
export default class MediaEntry extends Component {
static fragments = mergeFragments({
mediaItem: {
_id: 1,
name: 1,
date: 1
}
}, MediaItemPreview.fragments);
static propTypes = {
onClick: PropTypes.func.isRequired,
mediaItem: PropTypes.object.isRequired,
selected: PropTypes.bool.isRequired
};
@bind
onClick () {
const {onClick, mediaItem} = this.props;
onClick(mediaItem._id);
}
render () {
const {mediaItem, selected} = this.props;
const momentDate = moment(mediaItem.date);
return (
{mediaItem.name}
{momentDate.fromNow()}
);
}
}
================================================
FILE: lib/shared/screens/admin/shared/components/media-selector/entry.less
================================================
@import '~styles/colors.less';
.entry {
display: inline-block;
vertical-align: top;
width: 21.25%;
margin-right: 5%;
margin-bottom: 60px;
border: 1px solid transparent;
cursor: pointer;
}
@media screen and (min-width: 1501px) {
.entry {
width: 16%;
&:nth-child(5n+5) {
margin-right: 0;
}
}
}
@media screen and (max-width: 1500px) and (min-width: 1101px) {
.entry {
&:nth-child(4n+4) {
margin-right: 0;
}
}
}
@media screen and (max-width: 1100px) and (min-width: 851px) {
.entry {
width: 30%;
&:nth-child(3n+3) {
margin-right: 0;
}
}
}
@media screen and (max-width: 850px) and (min-width: 751px) {
.entry {
width: 45%;
margin-right: 10%;
&:nth-child(2n+2) {
margin-right: 0;
}
}
}
@media screen and (max-width: 750px) {
.entry {
width: 100%;
margin-right: 0%;
}
}
.preview {
width: 100%;
height: 125px;
background-color: @adminBorders;
}
.info {
padding: 15px;
height: 65px;
text-align: center;
}
.title {
color: @adminTextHighlight;
font-size: 16px;
line-height: 19px;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.date {
color: @adminText;
font-size: 12px;
font-weight: 300;
text-transform: uppercase;
}
.entry:hover {
border: 1px solid @adminBorders;
}
.entry.selected {
border: 1px solid @primary;
}
================================================
FILE: lib/shared/screens/admin/shared/components/media-selector/filters.jsx
================================================
import forEach from 'lodash.foreach';
import Combobox from 'components/input-options/combobox';
import Component from 'components/component';
import React, {PropTypes} from 'react';
import {getMimeTypes} from 'helpers/mime-types';
import styles from './filters.less';
export default class Filters extends Component {
static propTypes = {
changeSort: PropTypes.func.isRequired,
changeOrder: PropTypes.func.isRequired,
changeType: PropTypes.func.isRequired,
sort: PropTypes.string.isRequired,
order: PropTypes.string.isRequired,
type: PropTypes.string.isRequired,
allowedType: PropTypes.string.isRequired
}
sortChange (key, value) {
this.props.changeSort(key, value);
}
render () {
const {changeSort, changeOrder, changeType, sort, order, type, allowedType} = this.props;
const mimeFilterOptions = {
labels: ['ALL'],
values: ['all']
};
const mimeTypes = getMimeTypes(allowedType);
forEach(mimeTypes, (mimeType) => {
const splitted = mimeType.split('/');
if (splitted[1]) {
mimeFilterOptions.labels.push(splitted[1].toUpperCase());
mimeFilterOptions.values.push(mimeType);
}
});
return (
);
}
}
================================================
FILE: lib/shared/screens/admin/shared/components/media-selector/filters.less
================================================
@import '~styles/colors.less';
.mediumCombobox {
display: inline-block;
vertical-align: top;
width: 142px;
margin-right: 10px;
}
.smallCombobox {
display: inline-block;
vertical-align: top;
width: 88px;
}
.label {
text-transform: uppercase;
font-size: 10px;
color: @chromeTextColor;
line-height: 16px;
margin-bottom: 8px;
padding: 0 3px;
}
.option {
margin-bottom: 30px;
}
================================================
FILE: lib/shared/screens/admin/shared/components/media-selector/index.js
================================================
import * as mediaActions from 'actions/media';
import bind from 'decorators/bind';
import Component from 'components/component';
import React, {PropTypes} from 'react';
import {getMimeTypes} from 'helpers/mime-types';
import {bindActionCreators} from 'redux';
import {dataConnect} from 'relate-js';
import MediaSelector from './media-selector';
@dataConnect(
(state) => ({
uploads: state.media.uploads
}),
(dispatch) => bindActionCreators(mediaActions, dispatch),
() => ({
fragments: MediaSelector.fragments,
variablesTypes: {
media: {
sort: 'String',
order: 'String',
filters: '[Filter]'
}
},
initialVariables: {
media: {
sort: '_id',
order: 'desc'
}
},
mutations: {
addMedia: [
{
type: 'PREPEND',
field: 'media'
}
]
}
})
)
export default class MediaSelectorContainer extends Component {
static propTypes = {
selected: PropTypes.string,
media: PropTypes.array,
onClose: PropTypes.func.isRequired,
onChange: PropTypes.func.isRequired,
allowedType: PropTypes.string.isRequired,
uploadMediaFiles: PropTypes.func.isRequired,
uploads: PropTypes.array.isRequired
};
static defaultProps = {
media: []
};
getInitState () {
return {
sort: '_id',
order: 'desc',
type: 'all'
};
}
componentWillUpdate (nextProps, nextState) {
if (this.state.sort !== nextState.sort ||
this.state.order !== nextState.order ||
this.state.type !== nextState.type) {
const filter = nextState.type;
let filters = [];
if (filter !== 'all') {
let type = getMimeTypes(filter);
let op = 'in';
if (!type) {
op = 'eq';
type = filter;
}
filters = [{
property: 'type',
op: {
[op]: type
}
}];
}
nextProps.relate.setVariables({
media: {
sort: nextState.sort,
order: nextState.order,
filters
}
});
}
}
@bind
changeSort (sort) {
this.setState({sort});
}
@bind
changeOrder (order) {
this.setState({order});
}
@bind
changeType (type) {
this.setState({type});
}
render () {
const {media, selected, onClose, onChange, allowedType, uploadMediaFiles, uploads} = this.props;
return (
);
}
}
================================================
FILE: lib/shared/screens/admin/shared/components/media-selector/list.jsx
================================================
import Component from 'components/component';
import React, {PropTypes} from 'react';
import styles from './list.less';
import Entry from './entry';
import MockEntry from './mock-entry';
export default class MediaSelectorList extends Component {
static fragments = {
media: Entry.fragments.mediaItem
};
static propTypes = {
media: PropTypes.array.isRequired,
onChange: PropTypes.func.isRequired,
uploads: PropTypes.array.isRequired,
selected: PropTypes.string
};
render () {
const {uploads} = this.props;
const mocks = [];
for (let i = uploads.length - 1; i >= 0; i--) {
mocks.push(this.renderMockItem(uploads[i]));
}
return (
{mocks}
{this.props.media.map(this.renderItem, this)}
);
}
renderItem (mediaItem) {
const {onChange, selected} = this.props;
return (
);
}
renderMockItem (upload, index) {
const {status} = upload;
if (status === 'queue' || status === 'uploading') {
return (
);
}
}
}
================================================
FILE: lib/shared/screens/admin/shared/components/media-selector/list.less
================================================
.root {
padding: 40px;
}
================================================
FILE: lib/shared/screens/admin/shared/components/media-selector/media-selector.jsx
================================================
import Component from 'components/component';
import Modal from 'components/modal';
import Scrollable from 'components/scrollable';
import Upload from 'components/upload';
import React, {PropTypes} from 'react';
import styles from './media-selector.less';
import List from './list';
import Sidebar from './sidebar';
export default class MediasSelector extends Component {
static fragments = List.fragments;
static propTypes = {
media: PropTypes.array.isRequired,
selected: PropTypes.string,
onClose: PropTypes.func.isRequired,
onChange: PropTypes.func.isRequired,
changeSort: PropTypes.func.isRequired,
changeOrder: PropTypes.func.isRequired,
changeType: PropTypes.func.isRequired,
sort: PropTypes.string.isRequired,
order: PropTypes.string.isRequired,
type: PropTypes.string.isRequired,
allowedType: PropTypes.string.isRequired,
uploadMediaFiles: PropTypes.func.isRequired,
uploads: PropTypes.array.isRequired
};
render () {
const {media, selected, onClose, onChange, uploadMediaFiles, uploads} = this.props;
return (
{this.renderSidebar()}
);
}
renderSidebar () {
const {selected, changeSort, changeOrder, changeType, sort, order, type, allowedType} = this.props;
return (
);
}
}
================================================
FILE: lib/shared/screens/admin/shared/components/media-selector/media-selector.less
================================================
@import '~styles/colors.less';
.sidebar {
position: absolute;
top: 0; left: 0; bottom: 0;
width: 280px;
background-color: @chromeBackgroundColor;
}
.wrapper {
position: absolute;
top: 0; left: 280px; bottom: 0; right: 0;
}
================================================
FILE: lib/shared/screens/admin/shared/components/media-selector/mock-entry.jsx
================================================
import cx from 'classnames';
import Component from 'components/component';
import Spinner from 'components/spinner';
import React, {PropTypes} from 'react';
import mockStyles from './mock-entry.less';
import styles from './entry.less';
export default class MediaMockEntry extends Component {
static propTypes = {
upload: PropTypes.object.isRequired
};
render () {
const {upload} = this.props;
let result;
if (upload.status === 'queue') {
result = ;
} else if (upload.status === 'uploading') {
result = ;
}
return (
);
}
}
================================================
FILE: lib/shared/screens/admin/shared/components/media-selector/mock-entry.less
================================================
.root {
cursor: default;
&:hover {
border: 1px solid transparent !important;
}
}
.preview {
text-align: center;
padding: 50px 0;
:global i {
font-size: 18px;
color: #ccc;
}
}
================================================
FILE: lib/shared/screens/admin/shared/components/media-selector/selected/index.js
================================================
import Component from 'components/component';
import React, {PropTypes} from 'react';
import {dataConnect} from 'relate-js';
import Selected from './selected';
@dataConnect(
(props) => {
const result = {
fragments: {},
variablesTypes: {
mediaItem: {
id: 'ID!'
}
},
initialVariables: {
mediaItem: {
id: props.selected
}
}
};
if (props.selected) {
result.fragments = Selected.fragments;
}
return result;
}
)
export default class MediaSelectorSelectedContainer extends Component {
static propTypes = {
selected: PropTypes.string,
mediaItem: PropTypes.object
};
componentWillReceiveProps (nextProps) {
if (this.props.selected !== nextProps.selected && nextProps.selected) {
this.props.relate.refresh(nextProps);
}
}
render () {
const {mediaItem, selected} = this.props;
return (
);
}
}
================================================
FILE: lib/shared/screens/admin/shared/components/media-selector/selected/selected.jsx
================================================
import moment from 'moment';
import Component from 'components/component';
import MediaItemPreview from 'components/media-item-preview';
import React, {PropTypes} from 'react';
import {mergeFragments} from 'relate-js';
import styles from './selected.less';
export default class MediaSelectorSelected extends Component {
static fragments = mergeFragments({
mediaItem: {
_id: 1,
date: 1,
name: 1,
size: 1,
dimension: {
width: 1,
height: 1
}
}
}, MediaItemPreview.fragments);
static propTypes = {
selected: PropTypes.string,
mediaItem: PropTypes.object
};
render () {
const {mediaItem, selected} = this.props;
let result;
if (selected && mediaItem) {
const date = moment(mediaItem.date).format('Do MMMM YYYY');
result = (
{mediaItem.name}
{date}
{mediaItem.dimension && mediaItem.dimension.width &&
{`${mediaItem.dimension.width}x${mediaItem.dimension.height}`}
}
{mediaItem.size}
);
} else {
result = (
);
}
return result;
}
}
================================================
FILE: lib/shared/screens/admin/shared/components/media-selector/selected/selected.less
================================================
@import '~styles/colors.less';
.root {
position: absolute;
padding: 12px;
bottom: 52px;
left: 0;
right: 0;
height: 124px;
border-top: 1px solid @chromeBordersColor;
}
.wrapper {
display: table;
width: 100%;
table-layout: fixed;
}
.imagePart {
display: table-cell;
vertical-align: middle;
width: 100px;
height: 100px;
}
.dummyImage {
display: inline-block;
background-color: @chromeBordersColor;
width: 100px;
height: 100px;
}
.infoPart {
display: table-cell;
vertical-align: middle;
padding: 5px 10px;
padding-right: 0;
}
.title {
color: @chromeTextColor;
font-size: 11px;
margin-bottom: 5px;
word-break: break-word;
}
.underTitle {
font-size: 10px;
color: @chromeTextSubColor;
}
================================================
FILE: lib/shared/screens/admin/shared/components/media-selector/sidebar.jsx
================================================
import Button from 'components/button';
import Component from 'components/component';
import Scrollable from 'components/scrollable';
import React, {PropTypes} from 'react';
import styles from './sidebar.less';
import Filters from './filters';
import Selected from './selected';
export default class MediaSelectorSidebar extends Component {
static propTypes = {
selected: PropTypes.string,
changeSort: PropTypes.func.isRequired,
changeOrder: PropTypes.func.isRequired,
changeType: PropTypes.func.isRequired,
sort: PropTypes.string.isRequired,
order: PropTypes.string.isRequired,
type: PropTypes.string.isRequired,
allowedType: PropTypes.string.isRequired
};
render () {
const {selected, changeSort, changeOrder, changeType, sort, order, type, allowedType} = this.props;
return (
);
}
}
================================================
FILE: lib/shared/screens/admin/shared/components/media-selector/sidebar.less
================================================
@import '~styles/colors.less';
.header {
font-size: 16px;
padding: 0 10px;
line-height: 60px;
border-bottom: 1px solid @chromeBordersColor;
color: @chromeTextColor;
text-align: center;
}
.done {
position: absolute;
bottom: 0; left: 0; right: 0;
padding: 10px;
border-top: 1px solid @chromeBordersColor;
}
.filters {
position: absolute;
top: 61px;
bottom: 176px;
left: 0; right: 0;
}
.wrapper {
padding: 30px 20px;
}
================================================
FILE: lib/shared/screens/admin/shared/components/media-selector/top-bar.jsx
================================================
import Component from 'components/component';
import React from 'react';
import styles from './top-bar.less';
export default class MediaSelectorTopBar extends Component {
render () {
return (
topbar
);
}
}
================================================
FILE: lib/shared/screens/admin/shared/components/media-selector/top-bar.less
================================================
@import '~styles/colors.less';
.root {
height: 61px;
border-bottom: 1px solid @adminBorders;
}
================================================
FILE: lib/shared/screens/admin/shared/components/menu-button/index.jsx
================================================
import cx from 'classnames';
import Component from 'components/component';
import React, {PropTypes} from 'react';
import {Link} from 'react-router';
import styles from './index.less';
export default class Menu extends Component {
static propTypes = {
link: PropTypes.string.isRequired,
label: PropTypes.string.isRequired,
icon: PropTypes.string.isRequired,
active: PropTypes.bool,
onActiveClick: PropTypes.func,
dark: PropTypes.bool,
children: PropTypes.node,
query: PropTypes.object
};
static defaultProps = {
active: false
};
getInitState () {
this.caretToggle = ::this.caretToggle;
return {
opened: true
};
}
caretToggle () {
this.setState({
opened: !this.state.opened
});
}
onClick (event) {
if (this.props.active && this.props.onActiveClick) {
event.preventDefault();
event.stopPropagation();
this.props.onActiveClick();
}
}
render () {
const {link, label, icon, active, dark, query} = this.props;
return (
{label}
{this.renderCaret()}
{this.renderChildren()}
);
}
renderChildren () {
const {children} = this.props;
if (children && this.state.opened) {
return (
{children}
);
}
}
renderCaret () {
const {children} = this.props;
if (children) {
const {opened} = this.state;
return (
);
}
}
}
================================================
FILE: lib/shared/screens/admin/shared/components/menu-button/index.less
================================================
@import '~styles/colors.less';
.button {
display: block;
height: 60px;
font-size: 14px;
color: @adminText;
:global {
i, span {
display: inline-block;
vertical-align: top;
line-height: 60px;
}
i {
width: 60px;
text-align: center;
font-size: 22px;
color: @primarySub;
}
span {
font-weight: 600;
}
}
&:hover {
background-color: @primaryBack;
}
}
.dark {
:global i {
color: @adminText;
}
}
.buttonHolder {
position: relative;
}
.caret {
position: absolute;
right: 0;
top: 0;
height: 60px;
width: 60px;
text-align: center;
display: inline-block;
:global i {
color: @adminText;
font-size: 22px;
line-height: 60px;
}
&:hover {
:global i {
color: @adminTextHighlight;
}
}
}
.active {
background-color: @primary;
:global {
i, span {
color: #ffffff;
}
}
.caret {
:global {
color: #ffffff;
}
}
&:hover {
background-color: @primary;
}
}
================================================
FILE: lib/shared/screens/admin/shared/components/menu-sub-button/index.jsx
================================================
import cx from 'classnames';
import Component from 'components/component';
import React, {PropTypes} from 'react';
import {Link} from 'react-router';
import styles from './index.less';
export default class MenuSubButton extends Component {
static propTypes = {
link: PropTypes.string.isRequired,
label: PropTypes.string.isRequired,
active: PropTypes.bool,
query: PropTypes.object
};
static defaultProps = {
active: false
};
render () {
const {link, label, active, query} = this.props;
return (
{label}
);
}
}
================================================
FILE: lib/shared/screens/admin/shared/components/menu-sub-button/index.less
================================================
@import '~styles/colors.less';
.button {
line-height: 30px;
padding: 0 60px;
padding-right: 20px;
background-color: #F8F8F8;
color: @adminText;
display: block;
text-decoration: none;
font-size: 12px;
&:hover {
background-color: @primaryBack;
}
}
.active {
color: @primary;
}
================================================
FILE: lib/shared/screens/admin/shared/components/modal/index.jsx
================================================
import cx from 'classnames';
import Animate from 'components/animate';
import Component from 'components/component';
import Portal from 'components/portal';
import React, {PropTypes} from 'react';
import styles from './index.less';
export default class Modal extends Component {
static propTypes = {
children: PropTypes.node.isRequired,
onClose: PropTypes.func,
subTitle: PropTypes.string,
title: PropTypes.string,
small: PropTypes.bool,
big: PropTypes.bool
};
componentDidMount () {
const {onClose} = this.props;
if (onClose) {
this.keyDownBind = ::this.keyDown;
document.addEventListener('keydown', this.keyDownBind);
}
}
componentWillUnmount () {
this.keyDownBind && document.removeEventListener('keydown', this.keyDownBind);
}
keyDown (evt) {
const event = evt || window.event;
// Check if escape key pressed (code: 27)
if (event.keyCode === 27) {
this.props.onClose();
}
}
onClose () {
const {onClose} = this.props;
onClose && onClose();
}
render () {
const {children, small, big} = this.props;
return (
{this.renderTitles()}
{children}
);
}
renderTitles () {
const {title, subTitle} = this.props;
if (title || subTitle) {
return (
{subTitle &&
{subTitle}
}
{title &&
{title}
}
);
}
}
}
================================================
FILE: lib/shared/screens/admin/shared/components/modal/index.less
================================================
@import '~styles/colors.less';
.root {
position: fixed;
top: 0; bottom: 0;
left: 0; right: 0;
z-index: 10;
}
.background {
position: absolute;
background-color: rgba(0, 0, 0, 0.6);
top: 0; bottom: 0;
left: 0; right: 0;
}
.wrapper {
position: absolute;
top: 0; bottom: 0;
left: 0; right: 0;
pointer-events: none;
}
.content {
pointer-events: all;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background-color: #ffffff;
width: 725px;
max-width: 90%;
border-radius: 4px;
overflow: hidden;
&.small {
width: 430px;
}
&.big {
width: 90%;
height: 80%;
}
}
.titles {
padding: 20px 20px;
}
.subTitle {
font-size: 12px;
color: @adminText;
text-align: center;
text-transform: uppercase;
line-height: 18px;
margin-bottom: 5px;
}
.title {
font-size: 26px;
color: @adminText;
text-align: center;
line-height: 34px;
font-weight: 300;
}
================================================
FILE: lib/shared/screens/admin/shared/components/modal-delete/index.jsx
================================================
import Animate from 'components/animate';
import Button from 'components/button';
import Component from 'components/component';
import Spinner from 'components/spinner';
import React, {PropTypes} from 'react';
import styles from './index.less';
import Modal from '../modal';
export default class ModalDelete extends Component {
static propTypes = {
submit: PropTypes.func.isRequired,
cancel: PropTypes.func.isRequired,
loading: PropTypes.bool,
children: PropTypes.node.isRequired,
cancelLabel: PropTypes.string,
deleteLabel: PropTypes.string,
title: PropTypes.string,
subTitle: PropTypes.string
};
static defaultProps = {
cancelLabel: 'Cancel',
deleteLabel: 'Delete',
title: 'Are you sure?',
subTitle: 'You won\'t be able to reverse it'
};
onSubmit (event) {
event.preventDefault();
this.props.submit();
}
render () {
const {cancel, title, subTitle} = this.props;
return (
{this.renderLoading()}
{this.renderCreateButton()}
);
}
renderLoading () {
const {loading} = this.props;
return (
);
}
renderCreateButton () {
const {loading, cancelLabel, deleteLabel, cancel, submit} = this.props;
return (
);
}
}
================================================
FILE: lib/shared/screens/admin/shared/components/modal-delete/index.less
================================================
.button {
position: absolute;
top: 50%; left: 50%;
transform: translate(-50%, -50%);
}
.state {
position: relative;
margin-top: 15px;
margin-bottom: 30px;
height: 30px;
}
.out {
display: none;
}
================================================
FILE: lib/shared/screens/admin/shared/components/modal-input/index.jsx
================================================
import cx from 'classnames';
import Component from 'components/component';
import React, {PropTypes} from 'react';
import {findDOMNode} from 'react-dom';
import styles from './index.less';
export default class ModalInput extends Component {
static propTypes = {
invalid: PropTypes.bool,
value: PropTypes.string.isRequired,
onChange: PropTypes.func.isRequired,
placeholder: PropTypes.string,
focus: PropTypes.bool,
type: PropTypes.string
};
static defaultProps = {
type: 'text'
};
componentDidMount () {
if (this.props.focus) {
findDOMNode(this).focus();
}
}
onChange (event) {
this.props.onChange(event.target.value);
}
render () {
const {placeholder, value, invalid, type} = this.props;
return (
);
}
}
================================================
FILE: lib/shared/screens/admin/shared/components/modal-input/index.less
================================================
@import '~styles/colors.less';
.input {
display: block;
width: 100%;
border: 0;
outline: 0;
padding: 0 3px;
border-bottom: 1px solid @adminBordersInputs;
color: @adminText;
font-size: 16px;
line-height: 35px;
margin-bottom: 20px;
&::-webkit-input-placeholder {
color: @adminTextSub;
}
&:-moz-placeholder {
color: @adminTextSub;
}
&::-moz-placeholder {
color: @adminTextSub;
}
&:-ms-input-placeholder {
color: @adminTextSub;
}
&::-ms-input-placeholder {
color: @adminTextSub;
}
&:placeholder-shown {
color: @adminTextSub;
}
&:focus {
border-color: @primary;
}
}
.invalid {
border-color: @alert;
}
================================================
FILE: lib/shared/screens/admin/shared/components/modal-new/index.jsx
================================================
import Animate from 'components/animate';
import Button from 'components/button';
import Component from 'components/component';
import Spinner from 'components/spinner';
import React, {PropTypes} from 'react';
import styles from './index.less';
export default class ModalNew extends Component {
static propTypes = {
submit: PropTypes.func.isRequired,
loading: PropTypes.bool,
children: PropTypes.node.isRequired,
submitLabel: PropTypes.string
};
static defaultProps = {
submitLabel: 'Create'
};
onSubmit (event) {
event.preventDefault();
this.props.submit();
}
render () {
return (
);
}
renderLoading () {
const {loading} = this.props;
return (
);
}
renderCreateButton () {
const {loading, submitLabel} = this.props;
return (
);
}
}
================================================
FILE: lib/shared/screens/admin/shared/components/modal-new/index.less
================================================
.root {
text-align: center;
padding: 45px;
padding-bottom: 40px;
}
.button {
position: absolute;
top: 50%; left: 50%;
transform: translate(-50%, -50%);
}
.state {
position: relative;
margin-top: 30px;
height: 30px;
}
.out {
display: none;
}
================================================
FILE: lib/shared/screens/admin/shared/components/options-list/index.jsx
================================================
import cx from 'classnames';
import merge from 'lodash.merge';
import warning from 'warning';
import Component from 'components/component';
import React, {PropTypes} from 'react';
import {TypesOptionsMap, TypesOptionsDefaultProps} from 'helpers/input-options-map';
import styles from './index.less';
export default class OptionsList extends Component {
static propTypes = {
options: PropTypes.array.isRequired,
values: PropTypes.object.isRequired,
onChange: PropTypes.func.isRequired,
passToOptions: PropTypes.object,
white: PropTypes.bool,
tight: PropTypes.bool
};
static defaultProps = {
passToOptions: {}
};
onChange (id, value) {
this.props.onChange(id, value);
}
render () {
return this.renderOptions(this.props.options);
}
renderOptions (options) {
return (
{options.map(this.renderOption, this)}
);
}
renderColumn (option, index) {
return (
{this.renderOption(option)}
);
}
renderOption (option, index) {
let result;
if (option.type === 'Columns') {
result = (
{option.options.map(this.renderColumn, this)}
);
} else if (TypesOptionsMap[option.type]) {
const Option = TypesOptionsMap[option.type];
const value = this.props.values[option.id];
const extraProps = merge({}, TypesOptionsDefaultProps[option.type] || {});
merge(extraProps, option.props || {});
let unlockedContent = null;
if (option.unlocks && value !== '') {
if (option.type === 'Optional') {
if (value && option.unlocks.length > 1) {
unlockedContent = this.renderOptions(option.unlocks);
} else if (value && option.unlocks.length === 1) {
unlockedContent = (
{this.renderOptions(option.unlocks)}
);
}
extraProps.label = option.label;
} else if (option.type === 'Section') {
if (value) {
unlockedContent = {this.renderOptions(option.unlocks)}
;
}
extraProps.label = option.label;
} else if (option.unlocks.constructor === Array) {
unlockedContent = this.renderOptions(option.unlocks);
} else if (option.unlocks[value]) {
unlockedContent = this.renderOptions(option.unlocks[value]);
}
}
const onChange = this.onChange.bind(this, option.id);
if (option.type === 'Section') {
result = (
{unlockedContent}
);
} else {
result = (
{this.renderLabel(option.type !== 'Optional' && option.label)}
{unlockedContent}
);
}
} else {
warning(false, 'Element option type not valid');
}
return result;
}
renderLabel (label) {
if (label) {
const {white} = this.props;
return (
{label}
);
}
}
}
================================================
FILE: lib/shared/screens/admin/shared/components/options-list/index.less
================================================
@import '~styles/colors.less';
.option {
margin-bottom: 30px;
&.tight {
margin-bottom: 15px;
}
}
.option>.group {
margin-top: 20px;
padding-left: 10px;
}
.label {
text-transform: uppercase;
font-size: 10px;
color: @chromeTextColor;
line-height: 16px;
margin-bottom: 8px;
padding: 0 3px;
&.white {
color: @adminText;
}
:global {
i, span {
display: inline-block;
vertical-align: top;
line-height: 16px;
}
i {
margin-right: 7px;
font-size: 12px;
}
}
}
.columns {
display: table;
table-layout: fixed;
width: 100%;
&:last-child {
.option {
margin-bottom: 0;
}
}
}
.column {
display: table-cell;
vertical-align: top;
&:first-child {
padding-right: 10px;
}
&:last-child {
padding-left: 10px;
}
}
.section {
margin: 20px 0;
}
================================================
FILE: lib/shared/screens/admin/shared/components/options-menu/index.jsx
================================================
import Component from 'components/component';
import React from 'react';
export default class OptionsMenu extends Component {
static propTypes = {
options: React.PropTypes.array.isRequired,
style: React.PropTypes.object
};
render () {
return (
{this.props.options.map(this.renderOption, this)}
);
}
renderOption (option, key) {
return (
);
}
}
================================================
FILE: lib/shared/screens/admin/shared/components/page-builder/canvas/canvas.jsx
================================================
import displays from 'helpers/displays';
import forEach from 'lodash.foreach';
import getElementProps from 'helpers/get-element-props';
import stylesManager from 'helpers/styles-manager';
import utils from 'helpers/utils';
import Component from 'components/component';
import Droppable from 'components/dnd/droppable';
import Symbol from 'elements/symbol';
import React, {PropTypes} from 'react';
import {Component as Jss} from 'relax-jss';
import classes from './canvas.less';
import Empty from './empty';
export default class Canvas extends Component {
static propTypes = {
pageBuilderActions: PropTypes.object.isRequired,
display: PropTypes.string.isRequired,
styles: PropTypes.array.isRequired,
symbols: PropTypes.object.isRequired,
dragging: PropTypes.bool.isRequired,
pageData: PropTypes.object.isRequired,
elements: PropTypes.object.isRequired,
selectedId: PropTypes.string,
editing: PropTypes.bool.isRequired
};
static contextTypes = {
store: PropTypes.object.isRequired
};
static childContextTypes = {
dropHighlight: PropTypes.string.isRequired
};
getInitState () {
this.renderElementBind = ::this.renderElement;
this.renderChildrenBind = ::this.renderChildren;
return {};
}
getChildContext () {
const {dragging} = this.props;
return {
dropHighlight: dragging ? 'vertical' : 'none'
};
}
componentDidMount () {
this.refs.canvas.addEventListener('scroll', this.onScroll.bind(this));
}
onScroll () {
window.dispatchEvent(new Event('scroll'));
}
render () {
const {pageData, display} = this.props;
const dropInfo = {
id: 'body',
type: 'body'
};
const bodyStyle = {
margin: '0 auto',
maxWidth: displays[display]
};
// Process schema links if any
const elementsLinks = {};
const elements = pageData && pageData.body && this.renderChildren(pageData.body.children, {elementsLinks});
return (
{elements}
{this.renderStyles()}
);
}
renderPlaceholder () {
const {pageBuilderActions} = this.props;
return (
);
}
renderStyles () {
const styleTags = [];
forEach(stylesManager.stylesMap, (styleMap, key) => {
styleTags.push(
);
});
return styleTags;
}
renderChildren (children, options) {
let result;
if (children instanceof Array) {
result = children.map(this.renderElement.bind(this, options));
} else {
result = children;
}
return result;
}
renderElement (options, elementId, positionInParent) {
const {display, editing, pageData, elements, selectedId, styles} = this.props;
let element = options.customData && options.customData[elementId] || pageData[elementId];
const elementProps = getElementProps(element, display);
if (options.schemaEntry && options.elementsLinks && options.elementsLinks[element.id]) {
element = utils.alterSchemaElementProps(
options.elementsLinks[element.id],
element,
options.schemaEntry,
elementProps
);
}
const styleClassMap = stylesManager.processElement(
element,
elementProps,
elements[element.tag],
styles,
elements,
display
);
if ((!element.hide || !element.hide[display]) && element.display !== false) {
const FactoredElement = element.tag === 'Symbol' ? Symbol : elements[element.tag];
const selected = selectedId === element.id;
let children = element.children && this.renderChildren(element.children, options);
if (element.tag === 'Symbol') {
const symbol = this.props.symbols[element.props.symbolId];
children = symbol && symbol.data && this.renderElement({customData: symbol.data}, 'base', 0);
}
return (
{children}
);
}
}
}
================================================
FILE: lib/shared/screens/admin/shared/components/page-builder/canvas/canvas.less
================================================
.canvas {
position: absolute;
top: 0;
left: 0;
bottom: 0;
right: 0;
overflow-y: auto;
overflow-x: hidden;
}
.content {
box-shadow: 0 0 8px 4px fade(#000000, 20);
position: relative;
}
================================================
FILE: lib/shared/screens/admin/shared/components/page-builder/canvas/empty.jsx
================================================
import Component from 'components/component';
import React, {PropTypes} from 'react';
import styles from './empty.less';
export default class Empty extends Component {
static propTypes = {
pageBuilderActions: PropTypes.object.isRequired
};
onClick () {
const {pageBuilderActions} = this.props;
pageBuilderActions.addElementAt({tag: 'Section'}, {id: 'body', position: 0});
}
render () {
return (
Let's get you started
Click the blue dot below to add your first section
);
}
}
================================================
FILE: lib/shared/screens/admin/shared/components/page-builder/canvas/empty.less
================================================
@import '~styles/colors.less';
.root {
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
}
.wrapper {
position: absolute;
top: 30%;
left: 0;
right: 0;
text-align: center;
}
.title {
font-weight: 300;
font-size: 26px;
line-height: 36px;
color: @adminText;
}
.subTitle {
font-weight: 300;
font-size: 16px;
line-height: 34px;
color: @primary;
}
.button {
display: inline-block;
vertical-align: top;
width: 40px;
height: 40px;
border-radius: 50%;
background-color: @primary;
position: relative;
text-align: center;
margin-top: 15px;
transform: scale(0.5);
transition: all 0.2s ease-out;
:global i {
font-size: 14px;
color: #ffffff;
line-height: 40px;
opacity: 0;
transition: all 0.2s ease-out;
}
&:hover {
transform: scale(1);
:global i {
opacity: 1;
}
}
}
================================================
FILE: lib/shared/screens/admin/shared/components/page-builder/canvas/index.js
================================================
import * as pageBuilderActions from 'actions/page-builder';
import Component from 'components/component';
import React, {PropTypes} from 'react';
import {updateColors} from 'helpers/colors';
import {bindActionCreators} from 'redux';
import {dataConnect} from 'relate-js';
import Canvas from './canvas.jsx';
@dataConnect(
(state) => ({
dragging: state.dnd.dragging,
display: state.display,
symbols: state.symbols,
elements: state.pageBuilder.elements,
pageData: state.pageBuilder.data,
selectedId: state.pageBuilder.selectedId,
editing: state.pageBuilder.editing,
stylesData: state.styles.data
}),
(dispatch) => ({
pageBuilderActions: bindActionCreators(pageBuilderActions, dispatch)
}),
() => ({
fragments: {
colors: {
_id: 1,
label: 1,
value: 1
},
styles: {
_id: 1,
title: 1,
type: 1,
options: 1
}
}
})
)
export default class CanvasContainer extends Component {
static propTypes = {
pageBuilderActions: PropTypes.object.isRequired,
display: PropTypes.string.isRequired,
stylesData: PropTypes.array.isRequired,
symbols: PropTypes.object.isRequired,
dragging: PropTypes.bool.isRequired,
pageData: PropTypes.object.isRequired,
elements: PropTypes.object.isRequired,
selectedId: PropTypes.string,
editing: PropTypes.bool.isRequired,
colors: PropTypes.array.isRequired
};
getInitState () {
updateColors(this.props.colors);
}
componentWillReceiveProps (nextProps) {
if (this.props.colors !== nextProps.colors) {
updateColors(nextProps.colors);
}
}
render () {
const {
dragging,
display,
symbols,
elements,
pageData,
selectedId,
editing,
stylesData
} = this.props;
return (
);
}
}
================================================
FILE: lib/shared/screens/admin/shared/components/page-builder/elements-menu/autocomplete.jsx
================================================
import Component from 'components/component';
import React from 'react';
import styles from './autocomplete.less';
export default class Autocomplete extends Component {
static propTypes = {
autoFocus: React.PropTypes.bool,
value: React.PropTypes.string.isRequired,
onChange: React.PropTypes.func.isRequired,
suggestion: React.PropTypes.string
};
static defaultProps = {
autoFocus: true
};
componentDidMount () {
if (this.props.autoFocus) {
this.focus();
}
}
onInput () {
const str = this.refs.editable.innerText;
this.props.onChange(str);
}
focus () {
const el = this.refs.editable;
el.focus();
if (typeof window.getSelection !== 'undefined' && typeof document.createRange !== 'undefined') {
const range = document.createRange();
range.selectNodeContents(el);
range.collapse(false);
const sel = window.getSelection();
sel.removeAllRanges();
sel.addRange(range);
} else if (typeof document.body.createTextRange !== 'undefined') {
const range = document.body.createTextRange();
range.moveToElementText(el);
range.collapse(false);
range.select();
}
}
render () {
let before = '';
let after = '';
if (this.props.suggestion) {
const index = this.props.suggestion.toLowerCase().indexOf(this.props.value.toLowerCase());
before = index > 0 && this.props.suggestion.slice(0, index);
after = this.props.suggestion.slice(index + this.props.value.length);
}
return (
{before}
{this.props.value}
{after}
);
}
}
================================================
FILE: lib/shared/screens/admin/shared/components/page-builder/elements-menu/autocomplete.less
================================================
.autocomplete {
border: 0;
background-color: #000000;
border-radius: 0;
padding: 0 12px;
height: 40px;
:global span {
line-height: 40px;
color: #999999;
font-size: 20px;
border: 0;
outline: 0;
}
}
.editable {
color: #efefef;
}
================================================
FILE: lib/shared/screens/admin/shared/components/page-builder/elements-menu/elements-menu.jsx
================================================
import cx from 'classnames';
import forEach from 'lodash.foreach';
import Animate from 'components/animate';
import Component from 'components/component';
import React, {PropTypes} from 'react';
import {findDOMNode} from 'react-dom';
import styles from './elements-menu.less';
import List from './list';
import Search from './search';
export default class ElementsMenu extends Component {
static fragments = {
symbols: {
_id: 1,
title: 1
}
};
static propTypes = {
symbols: PropTypes.object.isRequired,
pageBuilderActions: PropTypes.object.isRequired,
elementsMenuOptions: PropTypes.object.isRequired,
elements: PropTypes.object.isRequired,
categories: PropTypes.object.isRequired
};
getInitState () {
return {
top: 0,
left: 0,
contentTop: 0,
side: 'right',
angleTriangle: false,
searchOpened: false,
search: '',
suggestions: [],
suggestion: false
};
}
componentDidMount () {
this.onCloseBind = ::this.onClose;
this.updatePositionBind = ::this.updatePosition;
this.stopPropagationBind = ::this.stopPropagation;
this.keyDownBind = ::this.focusSearch;
findDOMNode(this).addEventListener('click', this.stopPropagationBind);
document.addEventListener('keydown', this.keyDownBind);
document.addEventListener('click', this.onCloseBind);
window.addEventListener('scroll', this.updatePositionBind);
window.addEventListener('resize', this.updatePositionBind);
this.updatePosition();
}
componentWillUnmount () {
findDOMNode(this).removeEventListener('click', this.stopPropagationBind);
document.removeEventListener('keydown', this.keyDownBind);
document.removeEventListener('click', this.onCloseBind);
window.removeEventListener('scroll', this.updatePositionBind);
window.removeEventListener('resize', this.updatePositionBind);
}
focusSearch () {
if (!this.state.searchOpened) {
document.removeEventListener('keydown', this.keyDownBind);
this.setState({
searchOpened: true
});
}
}
onSearchChange (search) {
if (search) {
const {elements, categories} = this.props;
const suggestions = [];
const searchLowered = search.toLowerCase();
let suggestion = false;
let suggestionweight = categories.length + 2;
forEach(elements, (element, name) => {
if (this.elementAcceptable(name, element) && name.toLowerCase().indexOf(searchLowered) !== -1) {
let weight = categories.length;
forEach(categories, (category, ind) => {
if (category === (element.settings && element.settings.category)) {
weight = ind;
}
});
if (weight < suggestionweight) {
suggestion = name;
suggestionweight = weight;
}
suggestions.push(name);
}
});
forEach(this.props.symbols, (symbol) => {
if (symbol.title.toLowerCase().indexOf(searchLowered) !== -1) {
const weight = categories.length + 1;
if (weight < suggestionweight) {
suggestion = {
type: 'symbol',
id: symbol._id,
title: symbol.title
};
suggestionweight = weight;
}
suggestions.push({
type: 'symbol',
id: symbol._id
});
}
});
this.setState({
searchOpened: true,
search,
suggestions,
suggestion
});
} else {
this.state.searchOpened && document.addEventListener('keydown', this.keyDownBind);
this.setState({
searchOpened: false,
search
});
}
}
toggleCategory (category) {
const {toggleCategory} = this.props.pageBuilderActions;
toggleCategory(category);
}
stopPropagation () {
this.clickedInside = true;
}
updatePosition (event = null, props = this.props) {
const containerRect = props.elementsMenuOptions.container.getBoundingClientRect();
const top = containerRect.top + containerRect.height / 2 - 105;
let left = containerRect.right + 10;
let side = 'right';
let angleTriangle = false;
// Constraints
let contentTop = 0;
const menuWidth = 280;
const menuHeight = 400;
const windowHeight = window.innerHeight;
const windowWidth = window.innerWidth;
if (top + menuHeight > windowHeight) {
contentTop = windowHeight - (top + menuHeight);
if (contentTop < -360) {
contentTop = -360;
}
}
if (top < 46) {
const dif = 46 - top;
contentTop = dif;
if (contentTop > 95) {
contentTop = 115;
angleTriangle = true;
}
}
if (left + menuWidth > windowWidth) {
side = 'left';
left = containerRect.right - 10 - menuWidth - containerRect.width;
}
this.setState({top, left, contentTop, side, angleTriangle});
}
onClose () {
if (!this.clickedInside) {
this.props.pageBuilderActions.closeElementsMenu();
}
this.clickedInside = false;
}
addElement (tag) {
const {elementsMenuOptions} = this.props;
this.props.pageBuilderActions.closeElementsMenu();
this.props.pageBuilderActions.addElementAt({tag}, {
id: elementsMenuOptions.targetId,
position: elementsMenuOptions.targetPosition
});
}
addSymbol (symbolId) {
const {elementsMenuOptions} = this.props;
this.props.pageBuilderActions.closeElementsMenu();
this.props.pageBuilderActions.addElementAt({
tag: 'Symbol',
props: {
symbolId
}
}, {
id: elementsMenuOptions.targetId,
position: elementsMenuOptions.targetPosition
});
}
elementAcceptable (elementTag, element) {
const {elementsMenuOptions} = this.props;
let is = true;
if (elementsMenuOptions.accepts) {
if (elementsMenuOptions.accepts !== 'any' && elementsMenuOptions.accepts !== elementTag) {
is = false;
}
} else if (elementsMenuOptions.rejects) {
if (elementsMenuOptions.rejects === 'any' || elementsMenuOptions.rejects === elementTag) {
is = false;
}
}
const droppableOn = element.settings.drag && element.settings.drag.droppableOn;
if (droppableOn) {
if (droppableOn !== 'any' && elementsMenuOptions.targetType !== droppableOn) {
is = false;
}
}
return is;
}
render () {
const style = {
top: this.state.top,
left: this.state.left
};
const ballonStyle = {
top: this.state.contentTop
};
return (
);
}
renderContent () {
let result;
if (this.state.searchOpened) {
result = (
);
} else {
result = (
);
}
return result;
}
}
================================================
FILE: lib/shared/screens/admin/shared/components/page-builder/elements-menu/elements-menu.less
================================================
@import '~styles/colors.less';
.root {
width: 280px;
height: 400px;
text-align: left;
text-decoration: none;
z-index: 4;
position: fixed;
}
.angled {
.arrowLeft {
border-top: 11px solid transparent;
border-bottom: 11px solid transparent;
border-right: 17px solid @chromeBordersColor;
transform: translate(5px, 10px) rotate(45deg);
}
}
.left {
.arrowLeft {
left: auto;
right: -8px;
border-left: 8px solid @chromeBordersColor;
border-right: 0;
}
}
.arrowLeft {
position: absolute;
top: 96px;
left: -10px;
width: 0;
height: 0;
border-top: 9px solid transparent;
border-bottom: 9px solid transparent;
border-right: 11px solid #2b2d31;
z-index: 1;
}
.ballon {
width: 100%;
height: 100%;
border: 1px solid @chromeBordersColor;
background-color: #2b2d31;
border-radius: 3px;
overflow: hidden;
position: relative;
}
================================================
FILE: lib/shared/screens/admin/shared/components/page-builder/elements-menu/index.js
================================================
import * as pageBuilderActions from 'actions/page-builder';
import Component from 'components/component';
import React, {PropTypes} from 'react';
import {bindActionCreators} from 'redux';
import {dataConnect} from 'relate-js';
import ElementsMenu from './elements-menu';
@dataConnect(
(state) => ({
symbols: state.symbols,
elementsMenuOptions: state.pageBuilder.elementsMenuOptions,
elements: state.pageBuilder.elements,
categories: state.pageBuilder.categories,
categoriesCollapsed: state.pageBuilder.categoriesCollapsed
}),
(dispatch) => ({
pageBuilderActions: bindActionCreators(pageBuilderActions, dispatch)
}),
() => ({
fragments: ElementsMenu.fragments
})
)
export default class ElementsMenuContainer extends Component {
static propTypes = {
symbols: PropTypes.object.isRequired,
pageBuilderActions: PropTypes.object.isRequired
};
render () {
return ;
}
}
================================================
FILE: lib/shared/screens/admin/shared/components/page-builder/elements-menu/list.jsx
================================================
import cx from 'classnames';
import forEach from 'lodash.foreach';
import Component from 'components/component';
import Scrollable from 'components/scrollable';
import React, {PropTypes} from 'react';
import styles from './list.less';
export default class List extends Component {
static propTypes = {
elementAcceptable: PropTypes.func.isRequired,
addElement: PropTypes.func.isRequired,
addSymbol: PropTypes.func.isRequired,
toggleCategory: PropTypes.func.isRequired,
symbols: PropTypes.object.isRequired,
categories: PropTypes.object.isRequired,
categoriesCollapsed: PropTypes.object.isRequired,
elements: PropTypes.object.isRequired
};
toggleCategory (category, event) {
event.preventDefault();
this.props.toggleCategory(category);
}
addElement (tag, event) {
event.preventDefault();
this.props.addElement(tag);
}
addSymbol (symbolId) {
this.props.addSymbol(symbolId);
}
render () {
const {categories} = this.props;
return (
{categories.map(this.renderCategory, this)}
{this.renderSymbolsCategory()}
);
}
renderCategory (category) {
const {elements, categoriesCollapsed} = this.props;
const categoryElements = [];
forEach(elements, (element, index) => {
if (element.settings && element.settings.category) {
if (element.settings.category === category &&
this.props.elementAcceptable(index, element) &&
index !== 'Symbol') {
categoryElements.push({
label: index,
element
});
}
} else if (category === 'other' && this.props.elementAcceptable(index, element) && index !== 'Symbol') {
categoryElements.push({
label: index,
element
});
}
});
if (categoryElements.length > 0) {
const collapsedCategory = categoriesCollapsed[category];
const onClick = this.toggleCategory.bind(this, category);
return (
{category}
{!collapsedCategory && categoryElements.map(this.renderElement, this)}
);
}
}
renderElement (elementObj) {
const element = elementObj.element;
const icon = element.settings.icon;
const label = elementObj.label;
const addElement = this.addElement.bind(this, label);
return (
{icon.content}
{label}
);
}
renderSymbolsCategory () {
if (Object.keys(this.props.symbols).length > 0) {
const {categoriesCollapsed} = this.props;
const collapsedCategory = categoriesCollapsed.symbols;
const symbols = [];
forEach(this.props.symbols, (symbol) => {
symbols.push(this.renderSymbol(symbol));
});
const onClick = this.toggleCategory.bind(this, 'symbols');
return (
Symbols
{!collapsedCategory && symbols}
);
}
}
renderSymbol (symbol) {
const onClick = this.addSymbol.bind(this, symbol._id);
return (
{symbol.title}
);
}
}
================================================
FILE: lib/shared/screens/admin/shared/components/page-builder/elements-menu/list.less
================================================
@import '~styles/colors.less';
.root {
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
}
.category {
margin-top: -1px;
}
.collapsed {
.categoryInfo :global i {
transform: rotate(-90deg);
}
}
.categoryInfo {
background-color: @chromeBackgroundColor;
color: #efefef;
padding-right: 5px;
font-size: 10px;
text-transform: uppercase;
cursor: pointer;
line-height: 30px;
border-top: 1px solid @chromeBordersColor;
border-bottom: 1px solid @chromeBordersColor;
:global {
i, span {
vertical-align: top;
display: inline-block;
}
i {
color: #efefef;
line-height: 30px;
font-size: 14px;
width: 30px;
text-align: center;
transition: all 0.3s fade-out;
}
}
&:hover {
background-color: #3f4249;
}
}
.elementEntry {
height: 30px;
cursor: pointer;
padding-left: 25px;
:global {
i, span {
color: #efefef;
line-height: 30px;
display: inline-block;
vertical-align: top;
}
span {
font-size: 10px;
text-transform: uppercase;
}
i {
width: 35px;
font-size: 13px;
text-align: center;
}
}
&:hover, &.active {
background-color: #3f4249;
}
}
================================================
FILE: lib/shared/screens/admin/shared/components/page-builder/elements-menu/search.jsx
================================================
import forEach from 'lodash.foreach';
import key from 'keymaster';
import Component from 'components/component';
import Scrollable from 'components/scrollable';
import React, {PropTypes} from 'react';
import styles from './search.less';
import Autocomplete from './autocomplete';
export default class Search extends Component {
static propTypes = {
elementAcceptable: PropTypes.func.isRequired,
addElement: PropTypes.func.isRequired,
addSymbol: PropTypes.func.isRequired,
onSearchChange: PropTypes.func.isRequired,
suggestions: PropTypes.array.isRequired,
suggestion: PropTypes.string.isRequired,
search: PropTypes.string.isRequired,
symbols: PropTypes.object.isRequired,
categories: PropTypes.object.isRequired,
elements: PropTypes.object.isRequired
};
componentDidMount () {
key('enter', 'suggestions', this.addElementHotkey.bind(this, 0));
for (let i = 1; i < 10; i++) {
key(`⌘+${i}, ctrl+${i}`, 'suggestions', this.addElementHotkey.bind(this, i));
}
key.setScope('suggestions');
}
componentWillUnmount () {
key.unbind('enter');
for (let i = 1; i < 10; i++) {
key.unbind(`⌘+${i}, ctrl+${i}`);
}
key.setScope('all');
}
addElementHotkey (num, event) {
event.preventDefault();
event.stopPropagation();
const {suggestions} = this.props;
const {categories, elements} = this.props;
let counter = 0;
let added = false;
forEach(categories, (category) => {
forEach(suggestions, (suggestion) => {
if (typeof suggestion === 'string') {
const element = elements[suggestion];
const elementCategory = element.settings && element.settings.category || 'other';
if (category === elementCategory) {
if (counter === num) {
this.props.addElement(suggestion);
added = true;
} else {
counter++;
}
}
}
if (added) {
return false;
}
});
if (added) {
return false;
}
});
if (!added) {
forEach(suggestions, (suggestion) => {
if (typeof suggestion !== 'string') {
if (counter === num) {
this.props.addSymbol(suggestion.id);
added = true;
} else {
counter++;
}
}
if (added) {
return false;
}
});
}
}
addElement (tag, event) {
event.preventDefault();
this.props.addElement(tag);
}
addSymbol (symbolId) {
this.props.addSymbol(symbolId);
}
render () {
this.suggestionsCounter = -1;
return (
);
}
renderContent () {
let result;
if (this.props.suggestions.length > 0) {
const {categories} = this.props;
result = (
{categories.map(this.renderCategory, this)}
{this.renderSymbols()}
);
} else {
result = No results from your search
;
}
return result;
}
renderCategory (category) {
const {suggestions, elements} = this.props;
const categoryElements = [];
forEach(suggestions, (elementName) => {
if (typeof elementName === 'string') {
const element = elements[elementName];
if (element.settings && element.settings.category) {
if (element.settings.category === category) {
categoryElements.push({
label: elementName,
element
});
}
} else if (category === 'other') {
categoryElements.push({
label: elementName,
element
});
}
}
});
if (categoryElements.length > 0) {
return (
{category}
{categoryElements.map(this.renderElement, this)}
);
}
}
renderElement (elementObj) {
const element = elementObj.element;
const icon = element.settings.icon;
const label = elementObj.label;
const index = label.toLowerCase().indexOf(this.props.search.toLowerCase());
const before = index > 0 && label.slice(0, index);
const searched = label.slice(index, index + this.props.search.length);
const after = label.slice(index + this.props.search.length);
this.suggestionsCounter++;
return (
{icon.content}
{before}
{searched}
{after}
{this.suggestionsCounter < 10 && {this.suggestionsCounter === 0 ? 'enter' : ('cmd+' + this.suggestionsCounter)}}
);
}
renderSymbols () {
const {suggestions} = this.props;
const symbolsSuggestions = [];
forEach(suggestions, (suggestion) => {
if (typeof suggestion !== 'string') {
symbolsSuggestions.push(this.renderSymbol(suggestion.id));
}
});
if (symbolsSuggestions.length > 0) {
return (
Symbols
{symbolsSuggestions}
);
}
}
renderSymbol (id) {
const symbol = this.props.symbols[id];
const label = symbol.title;
const index = label.toLowerCase().indexOf(this.props.search.toLowerCase());
const before = index > 0 && label.slice(0, index);
const searched = label.slice(index, index + this.props.search.length);
const after = label.slice(index + this.props.search.length);
this.suggestionsCounter++;
return (
{before}
{searched}
{after}
{this.suggestionsCounter < 10 && {this.suggestionsCounter === 0 ? 'enter' : ('cmd+' + this.suggestionsCounter)}}
);
}
}
================================================
FILE: lib/shared/screens/admin/shared/components/page-builder/elements-menu/search.less
================================================
@import '~styles/colors.less';
.searchList {
position: absolute;
top: 40px;
bottom: 0;
left: 0;
right: 0;
}
.noResults {
padding: 30px 15px;
text-align: center;
font-size: 10px;
color: #efefef;
}
.suggestionCategory {
margin-top: 5px;
}
.categoryInfo {
color: #efefef;
font-size: 10px;
text-transform: uppercase;
padding: 12px;
}
.elementEntry {
padding: 0 18px;
cursor: pointer;
.searched {
color: @primary;
font-weight: 800;
}
.hotkey {
float: right;
color: #999999;
font-size: 8px;
text-transform: uppercase;
}
:global {
span, i {
display: inline-block;
vertical-align: top;
color: #efefef;
line-height: 30px;
font-size: 12px;
}
i {
font-size: 13px;
padding-right: 10px;
}
}
&:hover, &.active {
background-color: #3f4249;
}
}
================================================
FILE: lib/shared/screens/admin/shared/components/page-builder/index.js
================================================
import * as pageBuilderActions from 'actions/page-builder';
import Component from 'components/component';
import React, {PropTypes} from 'react';
import {bindActionCreators} from 'redux';
import {dataConnect} from 'relate-js';
import PageBuilder from './page-builder.jsx';
@dataConnect(
(state) => ({
params: state.router.params,
elementsMenuOpened: state.pageBuilder.elementsMenuOpened,
dragging: state.dnd.dragging
}),
(dispatch) => ({
pageBuilderActions: bindActionCreators(pageBuilderActions, dispatch)
}),
(props) => ({
fragments: {
draft: {
_id: {
_id: 1,
_userId: 1
},
__v: 1,
data: 1,
actions: 1
}
},
variablesTypes: {
draft: {
id: 'ID!'
}
},
initialVariables: {
draft: {
id: props.params.id
}
}
})
)
export default class PageBuilderContainer extends Component {
static propTypes = {
relate: PropTypes.object.isRequired,
params: PropTypes.object.isRequired
};
componentWillReceiveProps (nextProps) {
if (this.props.params.id !== nextProps.params.id) {
this.props.relate.setVariables({
draft: {
id: nextProps.params.id
}
});
}
}
render () {
return (
);
}
}
================================================
FILE: lib/shared/screens/admin/shared/components/page-builder/page-builder.jsx
================================================
import cx from 'classnames';
import key from 'keymaster';
import stylesheet from 'helpers/stylesheet';
import Component from 'components/component';
import Dragger from 'components/dnd/dragger';
import Portal from 'components/portal';
import React, {PropTypes} from 'react';
import {Component as Jss} from 'relax-jss';
import styles from './page-builder.less';
import Canvas from './canvas';
import ElementsMenu from './elements-menu';
export default class PageBuilder extends Component {
static propTypes = {
dragging: PropTypes.bool.isRequired,
elementsMenuOpened: PropTypes.bool.isRequired,
pageBuilderActions: PropTypes.object.isRequired
};
componentDidMount () {
const {undoAction, redoAction} = this.props.pageBuilderActions;
key('⌘+z, ctrl+z', undoAction);
key('⌘+y, ctrl+y', redoAction);
// key('delete', );
}
componentWillUnmount () {
key.unbind('⌘+z, ctrl+z');
key.unbind('⌘+y, ctrl+y');
}
render () {
return (
{this.renderElementsMenu()}
{this.renderDragger()}
);
}
renderElementsMenu () {
const {elementsMenuOpened} = this.props;
if (elementsMenuOpened) {
return (
);
}
}
renderDragger () {
const {dragging, pageBuilderActions} = this.props;
if (dragging) {
return (
);
}
}
}
================================================
FILE: lib/shared/screens/admin/shared/components/page-builder/page-builder.less
================================================
================================================
FILE: lib/shared/screens/admin/shared/components/page-builder-menu/breadcrumbs/breadcrumbs.jsx
================================================
import cx from 'classnames';
import Component from 'components/component';
import React, {PropTypes} from 'react';
import styles from './breadcrumbs.less';
import Entry from './entry';
export default class Breadcrumbs extends Component {
static propTypes = {
selectElement: PropTypes.func.isRequired,
selectedPath: PropTypes.array.isRequired,
selectedElement: PropTypes.object,
className: PropTypes.string
};
render () {
const {selectedPath, selectedElement, className} = this.props;
return (
{selectedPath && selectedPath.map(this.renderEntry, this)}
{(selectedElement && (selectedElement.label || selectedElement.tag)) || 'body'}
);
}
renderEntry (entry) {
const {selectElement} = this.props;
return (
);
}
}
================================================
FILE: lib/shared/screens/admin/shared/components/page-builder-menu/breadcrumbs/breadcrumbs.less
================================================
@import '~styles/colors.less';
.root {
overflow: hidden;
white-space: nowrap;
direction: ltr;
text-align: left;
&:after {
content: '';
position: absolute;
left: 0; top: 0; bottom: 0;
width: 7px;
background: linear-gradient(to right, rgba(51,54,59,1) 0%, rgba(51,54,59,0) 100%);
}
}
.current {
font-size: 12px;
color: @chromeTextSubColor;
}
================================================
FILE: lib/shared/screens/admin/shared/components/page-builder-menu/breadcrumbs/entry.jsx
================================================
import Component from 'components/component';
import React, {PropTypes} from 'react';
import styles from './entry.less';
export default class Breadcrumbs extends Component {
static propTypes = {
entry: PropTypes.object.isRequired,
onClick: PropTypes.func.isRequired
};
onClick () {
const {onClick, entry} = this.props;
onClick(entry.id);
}
render () {
const {entry} = this.props;
return (
>
);
}
}
================================================
FILE: lib/shared/screens/admin/shared/components/page-builder-menu/breadcrumbs/entry.less
================================================
@import '~styles/colors.less';
.root {
font-size: 12px;
}
.button {
position: relative;
text-decoration: none;
font-size: 12px;
color: @chromeTextColor;
text-transform: capitalize;
&:hover {
color: @primary;
}
}
.sep {
font-size: 12px;
color: @chromeTextColor;
}
================================================
FILE: lib/shared/screens/admin/shared/components/page-builder-menu/breadcrumbs/index.js
================================================
import Component from 'components/component';
import React from 'react';
import {selectElement} from 'actions/page-builder';
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
import Breadcrumbs from './breadcrumbs';
@connect(
(state) => ({
selectedPath: state.pageBuilder.selectedPath,
selectedElement: state.pageBuilder.selectedElement
}),
(dispatch) => bindActionCreators({selectElement}, dispatch)
)
export default class BreadcrumbsContainer extends Component {
render () {
return (
);
}
}
================================================
FILE: lib/shared/screens/admin/shared/components/page-builder-menu/index.js
================================================
import Component from 'components/component';
import React from 'react';
import {connect} from 'react-redux';
import Menu from './menu';
@connect(
(state) => ({
linkingData: state.pageBuilder.linkingData,
linkingFormData: state.pageBuilder.linkingFormData,
editing: state.pageBuilder.editing
})
)
export default class PageBuilderMenuContainer extends Component {
render () {
return (
);
}
}
================================================
FILE: lib/shared/screens/admin/shared/components/page-builder-menu/menu.jsx
================================================
import velocity from 'velocity-animate';
import Component from 'components/component';
import React, {PropTypes} from 'react';
import styles from './menu.less';
import Breadcrumbs from './breadcrumbs';
import Tabs from './tabs';
export default class PageBuilderMenu extends Component {
static propTypes = {
editing: PropTypes.bool.isRequired,
linkingData: PropTypes.bool.isRequired,
linkingFormData: PropTypes.bool.isRequired,
previewing: PropTypes.bool.isRequired
};
componentWillReceiveProps (nextProps) {
const config = {
duration: 800,
display: null,
easing: 'easeOutExpo'
};
if (nextProps.previewing !== this.props.previewing) {
if (nextProps.previewing) {
velocity(this.refs.content, {translateX: '290px'}, config);
} else {
velocity(this.refs.content, {translateX: '0px'}, config);
}
}
}
render () {
return (
);
}
renderContent () {
const {linkingData, linkingFormData} = this.props;
let result;
if (linkingData) {
result = Missing
;
} else if (linkingFormData) {
result = Missing
;
} else {
result = ;
}
return result;
}
}
================================================
FILE: lib/shared/screens/admin/shared/components/page-builder-menu/menu.less
================================================
@import '~styles/sizes.less';
@import '~styles/colors.less';
@breadcrumbsHeight: 30px;
.root {
position: absolute;
left: 100%;
top: 0;
bottom: 0;
width: @menuWidth;
background-color: @chromeBackgroundColor;
border-left: 1px solid @chromeBordersColor;
}
.breadcrumbs {
position: absolute;
height: @breadcrumbsHeight;
line-height: @breadcrumbsHeight;
border-top: 1px solid @chromeBordersColor;
padding: 0 7px;
bottom: 0;
left: 0;
right: 0;
}
.content {
position: absolute;
top: 0px;
left: 0;
right: 0;
bottom: @breadcrumbsHeight;
}
================================================
FILE: lib/shared/screens/admin/shared/components/page-builder-menu/tabs/index.js
================================================
import Component from 'components/component';
import React from 'react';
import {setMenuTab} from 'actions/page-builder';
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
import Tabs from './tabs';
@connect(
(state) => ({
menuTab: state.pageBuilder.menuTab
}),
(dispatch) => bindActionCreators({setMenuTab}, dispatch)
)
export default class TabsContainer extends Component {
render () {
return (
);
}
}
================================================
FILE: lib/shared/screens/admin/shared/components/page-builder-menu/tabs/layers/entry.jsx
================================================
import cx from 'classnames';
import Component from 'components/component';
import Draggable from 'components/dnd/draggable';
import OptionsMenu from 'components/options-menu';
import React, {PropTypes} from 'react';
import styles from './entry.less';
export default class Entry extends Component {
static propTypes = {
pageBuilderActions: PropTypes.object.isRequired,
element: PropTypes.object.isRequired,
isExpanded: PropTypes.bool.isRequired,
hasChildren: PropTypes.bool.isRequired,
dragging: PropTypes.bool.isRequired,
ElementClass: PropTypes.func.isRequired,
selectedId: PropTypes.string,
overedId: PropTypes.string
};
getInitState () {
return {
options: false
};
}
onClick () {
const {element, pageBuilderActions} = this.props;
pageBuilderActions.selectElement(element.id);
}
onMouseOver () {
const {dragging, pageBuilderActions, element, hasChildren, isExpanded} = this.props;
if (!dragging) {
pageBuilderActions.overElement(element.id);
} else if (hasChildren && !isExpanded) {
this.openInterval = setTimeout(pageBuilderActions.toggleExpandElement.bind(this, element.id), 500);
}
}
onMouseOut () {
const {dragging, pageBuilderActions, element} = this.props;
if (!dragging) {
pageBuilderActions.outElement(element.id);
if (this.state.options) {
this.setState({
options: false
});
}
} else if (this.openInterval) {
clearTimeout(this.openInterval);
}
}
openOptions (event) {
event.preventDefault();
event.stopPropagation();
this.setState({
options: true
});
}
duplicate (event) {
event.preventDefault();
const {pageBuilderActions, element} = this.props;
pageBuilderActions.duplicateElement(element.id);
this.setState({
options: false
});
}
remove (event) {
event.preventDefault();
const {pageBuilderActions, element} = this.props;
pageBuilderActions.removeElement(element.id);
this.setState({
options: false
});
}
toggleExpand (event) {
event.preventDefault();
event.stopPropagation();
const {pageBuilderActions, element} = this.props;
pageBuilderActions.toggleExpandElement(element.id);
}
render () {
const {ElementClass, element} = this.props;
let result;
if (element.subComponent) {
result = (
{this.renderContent()}
);
} else {
const dragInfo = {
type: 'move',
id: element.id
};
result = (
{this.renderContent()}
);
}
return result;
}
renderOptionsMenu () {
if (this.state.options) {
return (
);
}
}
renderContent () {
const {ElementClass, selectedId, overedId, element, hasChildren} = this.props;
const selected = selectedId === element.id;
const overed = overedId === element.id;
const subComponent = element.subComponent;
return (
{this.renderCaret()}
{ElementClass.settings.icon.content}
{element.label || element.tag}
{this.renderOptions()}
);
}
renderCaret () {
const {hasChildren, isExpanded} = this.props;
if (hasChildren) {
return (
);
}
}
renderOptions () {
if (!this.props.element.subComponent) {
return (
{this.renderOptionsMenu()}
);
}
}
}
================================================
FILE: lib/shared/screens/admin/shared/components/page-builder-menu/tabs/layers/entry.less
================================================
@import '~styles/colors.less';
@height: 30px;
.entry {
display: block;
text-align: left;
text-decoration: none;
color: @chromeTextColor;
font-size: 11px;
line-height: @height;
padding-right: 25px;
margin-top: 1px;
height: @height;
&.subComponent {
opacity: 0.6;
}
&:not(.hasChildren) {
.info {
padding-left: @height;
}
}
&:before {
content: '';
position: absolute;
left: 0; right: 0;
height: @height;
border-bottom: 1px solid @chromeBordersColor;
}
&.overed {
&:before {
background-color: @chromeBackgroundActive;
}
.options {
visibility: visible;
}
}
&.selected {
&:before {
background-color: @primary;
}
color: @chromeTextColorHighlight;
}
}
.part {
position: relative;
vertical-align: top;
display: inline-block;
}
.caret {
width: @height;
height: @height;
text-align: center;
cursor: pointer;
:global i {
color: @chromeTextColor;
font-size: 13px;
line-height: @height;
transition: all 0.2s ease-out;
}
&.collapsed {
:global i {
transform: rotate(-90deg);
}
}
}
.info {
cursor: default;
:global {
i, span {
display: inline-block;
vertical-align: top;
line-height: @height;
color: @chromeTextColor;
}
i {
font-size: 13px;
padding-right: 10px;
}
span {
font-size: 11px;
}
}
}
.options {
position: absolute;
right: 0;
height: @height;
width: @height;
text-align: center;
cursor: pointer;
:global i {
line-height: @height;
}
}
================================================
FILE: lib/shared/screens/admin/shared/components/page-builder-menu/tabs/layers/index.js
================================================
import * as pageBuilderActions from 'actions/page-builder';
import Component from 'components/component';
import React from 'react';
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
import Layers from './layers';
@connect(
(state) => ({
data: state.pageBuilder.data,
selectedElement: state.pageBuilder.selectedElement,
elements: state.pageBuilder.elements,
expanded: state.pageBuilder.expanded,
userExpanded: state.pageBuilder.userExpanded,
selectedId: state.pageBuilder.selectedId,
overedId: state.pageBuilder.overedId,
dragging: state.dnd.dragging
}),
(dispatch) => ({
pageBuilderActions: bindActionCreators(pageBuilderActions, dispatch)
})
)
export default class LayersTabContainer extends Component {
render () {
return (
);
}
}
================================================
FILE: lib/shared/screens/admin/shared/components/page-builder-menu/tabs/layers/layers.jsx
================================================
import Component from 'components/component';
import Droppable from 'components/dnd/droppable';
import Scrollable from 'components/scrollable';
import React, {PropTypes} from 'react';
import styles from './layers.less';
import Entry from './entry';
export default class Layers extends Component {
static propTypes = {
pageBuilderActions: PropTypes.object.isRequired,
data: PropTypes.object.isRequired,
elements: PropTypes.object.isRequired,
expanded: PropTypes.object.isRequired,
userExpanded: PropTypes.object.isRequired,
dragging: PropTypes.bool.isRequired,
selectedId: PropTypes.string,
overedId: PropTypes.string
};
render () {
const {data, pageBuilderActions} = this.props;
return (
{
data.body &&
data.body.children &&
this.renderList(
data.body.children,
{type: 'body', id: 'body'},
{accepts: 'Section'},
{tag: 'body'}
)
}
);
}
renderList (children, dropInfo, dropSettings, parent, droppable = true) {
let result;
if (!droppable) {
result = (
{children.map(this.renderListEntry, this)}
);
} else {
result = (
{children.map(this.renderListEntry, this)}
);
}
return result;
}
renderListEntry (elementId) {
const {
elements,
data,
expanded,
userExpanded,
dragging,
pageBuilderActions,
selectedId,
overedId
} = this.props;
const element = data[elementId];
const hasChildren = element.children instanceof Array && element.children.length > 0;
const ElementClass = elements[element.tag];
const dropInfo = {id: element.id};
let dropSettings = ElementClass.settings.drop;
const isExpanded = hasChildren && (expanded[elementId] || userExpanded[elementId]) && true;
if (dropSettings !== false) {
dropSettings = Object.assign({}, ElementClass.settings.drop, {
orientation: 'vertical',
customDropArea: false,
selectionChildren: false
});
}
let underlings;
if (isExpanded) {
underlings = this.renderList(element.children, dropInfo, dropSettings, element, dropSettings);
} else if (dragging && !hasChildren && dropSettings !== false) {
underlings = (
);
}
return (
{underlings}
);
}
}
================================================
FILE: lib/shared/screens/admin/shared/components/page-builder-menu/tabs/layers/layers.less
================================================
@import '~styles/colors.less';
.filterDisplay {
padding: 0 10px;
}
.trigger {
display: inline-block;
vertical-align: top;
text-decoration: none;
font-size: 10px;
color: @chromeTextColor;
margin-right: 10px;
opacity: 0.7;
line-height: 30px;
&:hover, &.active {
opacity: 1;
}
}
.structureList {
border-top: 1px solid @chromeBordersColor;
}
.list {
list-style-type: none;
padding: 0;
margin: 0;
.list {
padding-left: 15px;
}
}
================================================
FILE: lib/shared/screens/admin/shared/components/page-builder-menu/tabs/settings/animation.jsx
================================================
import Button from 'components/button';
import Component from 'components/component';
import OptionsList from 'components/options-list';
import React, {PropTypes} from 'react';
export default class AnimationTab extends Component {
static propTypes = {
pageBuilderActions: PropTypes.object.isRequired,
selectedId: PropTypes.string,
selectedElement: PropTypes.object
};
static options = [
{
label: 'Animation',
type: 'Optional',
id: 'use',
unlocks: [
{
label: 'Effect',
type: 'Select',
id: 'effect',
props: {
labels: [
'Fade',
'Flip X',
'Flip Y',
'Whirl',
'Shrink',
'Expand',
'Slide up',
'Slide down',
'Slide left',
'Slide right',
'Slide big up',
'Slide big down',
'Slide big left',
'Slide big right'
],
values: [
'transition.fadeIn',
'transition.flipXIn',
'transition.flipYIn',
'transition.whirlIn',
'transition.shrinkIn',
'transition.expandIn',
'transition.slideUpIn',
'transition.slideDownIn',
'transition.slideLeftIn',
'transition.slideRightIn',
'transition.slideUpBigIn',
'transition.slideDownBigIn',
'transition.slideLeftBigIn',
'transition.slideRightBigIn'
]
}
},
{
type: 'Columns',
options: [
{
label: 'Duration',
type: 'Number',
id: 'duration',
props: {
min: 0,
max: 20000,
label: 'ms'
}
},
{
label: 'Delay',
type: 'Number',
id: 'delay',
props: {
min: 0,
max: 20000,
label: 'ms'
}
}
]
}
]
}
];
onChange (id, value) {
const {selectedId} = this.props;
const {changeElementAnimation} = this.props.pageBuilderActions;
changeElementAnimation(selectedId, id, value);
}
playAnimations (event) {
event.preventDefault();
window.dispatchEvent(new Event('animateElements'));
}
render () {
return (
{this.renderContent()}
);
}
renderContent () {
const {selectedElement} = this.props;
return (
);
}
}
================================================
FILE: lib/shared/screens/admin/shared/components/page-builder-menu/tabs/settings/index.js
================================================
import * as pageBuilderActions from 'actions/page-builder';
import Component from 'components/component';
import React, {PropTypes} from 'react';
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
import Settings from './settings';
@connect(
(state) => ({
selectedId: state.pageBuilder.selectedId,
selectedElement: state.pageBuilder.selectedElement,
elements: state.pageBuilder.elements,
display: state.display
}),
(dispatch) => ({
pageBuilderActions: bindActionCreators(pageBuilderActions, dispatch)
})
)
export default class SettingsTabContainer extends Component {
static propTypes = {
pageBuilderActions: PropTypes.object.isRequired,
selectedId: PropTypes.string
};
duplicate () {
const {selectedId} = this.props;
const {duplicateElement} = this.props.pageBuilderActions;
duplicateElement(selectedId);
}
remove () {
const {removeElement} = this.props.pageBuilderActions;
const {selectedId} = this.props;
removeElement(selectedId);
}
render () {
return (
);
}
}
================================================
FILE: lib/shared/screens/admin/shared/components/page-builder-menu/tabs/settings/position.jsx
================================================
import getElementPosition from 'helpers/get-element-position';
import Component from 'components/component';
import OptionsList from 'components/options-list';
import React, {PropTypes} from 'react';
const positioningOptions = [
{
type: 'Columns',
options: [
{
label: 'Top',
type: 'Number',
id: 'top',
props: {
allowed: ['px', '%', 'auto']
}
},
{
label: 'Right',
type: 'Number',
id: 'right',
props: {
allowed: ['px', '%', 'auto']
}
}
]
},
{
type: 'Columns',
options: [
{
label: 'Bottom',
type: 'Number',
id: 'bottom',
props: {
allowed: ['px', '%', 'auto']
}
},
{
label: 'Left',
type: 'Number',
id: 'left',
props: {
allowed: ['px', '%', 'auto']
}
}
]
},
{
label: 'Z-index',
type: 'Number',
id: 'zIndex'
}
];
export default class PositionSettings extends Component {
static propTypes = {
pageBuilderActions: PropTypes.object.isRequired,
display: PropTypes.string.isRequired,
selectedId: PropTypes.string,
selectedElement: PropTypes.object
};
static options = [
{
label: 'Position',
type: 'Select',
id: 'position',
props: {
labels: ['Static', 'Relative', 'Absolute', 'Fixed'],
values: ['static', 'relative', 'absolute', 'fixed']
},
unlocks: {
relative: positioningOptions,
absolute: positioningOptions,
fixed: positioningOptions
}
}
];
onChange (id, value) {
const {selectedId} = this.props;
const {changeElementPosition} = this.props.pageBuilderActions;
changeElementPosition(selectedId, id, value);
}
render () {
const {selectedElement} = this.props;
const values = selectedElement.position && getElementPosition(selectedElement, this.props.display) || {
position: 'static'
};
return (
);
}
}
================================================
FILE: lib/shared/screens/admin/shared/components/page-builder-menu/tabs/settings/props.jsx
================================================
import cx from 'classnames';
import getElementProps from 'helpers/get-element-props';
import optionsStyles from 'components/options-list/index.less';
import Component from 'components/component';
import Input from 'components/input-options/input';
import OptionsList from 'components/options-list';
import React, {PropTypes} from 'react';
import styles from './props.less';
import Animation from './animation';
import Position from './position';
export default class EditProps extends Component {
static propTypes = {
pageBuilderActions: PropTypes.object.isRequired,
display: PropTypes.string.isRequired,
selectedElement: PropTypes.object,
selectedId: PropTypes.string,
elements: PropTypes.object.isRequired
};
displayToggleElement (id, display) {
const {toggleElementVisibleOn} = this.props.pageBuilderActions;
toggleElementVisibleOn(id, display);
}
render () {
const {selectedElement, elements} = this.props;
const {changeElementLabel} = this.props.pageBuilderActions;
const ElementClass = elements[selectedElement.tag];
return (
{this.renderOptions(ElementClass)}
);
}
renderOptions (ElementClass) {
const {changeElementProperty} = this.props.pageBuilderActions;
const {selectedId, selectedElement, display} = this.props;
if (ElementClass.propsSchema) {
const values = Object.assign({}, ElementClass.defaultProps, getElementProps(selectedElement, display));
return (
);
}
}
}
================================================
FILE: lib/shared/screens/admin/shared/components/page-builder-menu/tabs/settings/props.less
================================================
@import '~styles/colors.less';
.root {
padding: 15px;
padding-top: 20px;
}
.displayButton {
text-decoration: none;
display: inline-block;
color: #ffffff;
border: 1px solid @chromeBordersColor;
border-right-width: 0;
background-color: @chromeBackgroundDarkerColor;
width: 33.332%;
height: 38px;
text-align: center;
:global i {
font-size: 10px;
line-height: 38px;
}
&:first-child {
border-radius: 3px 0 0 3px;
}
&:last-child {
border-radius: 0 3px 3px 0;
border-right-width: 1px;
}
&:hover {
background-color: @chromeBackgroundActive;
}
&.selected {
background-color: #7a222d;
border-color: #7a222d;
}
}
================================================
FILE: lib/shared/screens/admin/shared/components/page-builder-menu/tabs/settings/settings.jsx
================================================
import Component from 'components/component';
import Scrollable from 'components/scrollable';
import React, {PropTypes} from 'react';
import styles from './settings.less';
import Props from './props';
export default class SettingsTab extends Component {
static propTypes = {
selectedId: PropTypes.string,
selectedElement: PropTypes.object,
duplicate: PropTypes.func.isRequired,
remove: PropTypes.func.isRequired
};
render () {
return (
{this.renderActionButtons()}
{this.renderContent()}
);
}
renderContent () {
const {selectedId} = this.props;
let result;
if (selectedId && selectedId !== 'body') {
result = ;
} else {
result = this.renderNonSelected();
}
return result;
}
renderNonSelected () {
return (
Relax, you have to select an element first!
);
}
renderActionButtons () {
const {selectedId, selectedElement, duplicate, remove} = this.props;
if (selectedId && selectedId !== 'body') {
let result;
if (selectedElement.subComponent) {
result = (
This is a sub element
);
} else {
result = (
);
}
return result;
}
}
}
================================================
FILE: lib/shared/screens/admin/shared/components/page-builder-menu/tabs/settings/settings.less
================================================
@import '~styles/colors.less';
.content {
position: absolute;
top: 30px;
bottom: 0px;
left: 0;
right: 0;
}
.info {
position: absolute;
left: 0;
right: 0;
top: 40%;
transform: translateY(-50%);
text-align: center;
padding: 0 45px;
:global i {
font-size: 35px;
color: @chromeTextSubColor;
margin-bottom: 15px;
}
}
.label {
font-size: 12px;
color: @chromeTextSubColor;
}
.actions {
position: absolute;
top: 0;
left: 0;
right: 0;
height: 30px;
border-bottom: 1px solid @chromeBordersColor;
margin: 0;
}
.action {
display: inline-block;
vertical-align: top;
text-align: center;
text-decoration: none;
width: 50%;
margin: 0;
border-right: 1px solid @chromeBordersColor;
:global {
i, span {
display: inline-block;
line-height: 30px;
color: @chromeTextColor;
}
span {
font-size: 10px;
}
i {
font-size: 9px;
margin-right: 6px;
}
}
&:last-child {
border-right: 0;
}
&:hover {
:global {
i, span {
color: @primary;
}
}
}
}
================================================
FILE: lib/shared/screens/admin/shared/components/page-builder-menu/tabs/style/index.js
================================================
import Component from 'components/component';
import React from 'react';
import {connect} from 'react-redux';
import Style from './style';
@connect(
(state) => ({
styles: state.styles.data,
display: state.display,
selectedElement: state.pageBuilder.selectedElement,
elements: state.pageBuilder.elements
})
)
export default class StyleTabContainer extends Component {
render () {
return (
);
}
}
================================================
FILE: lib/shared/screens/admin/shared/components/page-builder-menu/tabs/style/style-picker/edit.jsx
================================================
import getElementStyleValues from 'helpers/get-element-style-values';
import Component from 'components/component';
import OptionsList from 'components/options-list';
import React from 'react';
import styles from './edit.less';
export default class Edit extends Component {
static propTypes = {
display: React.PropTypes.string.isRequired,
styleOptions: React.PropTypes.object.isRequired,
selectedStyle: React.PropTypes.object.isRequired,
onChange: React.PropTypes.func.isRequired
};
render () {
return (
{this.renderOptions()}
);
}
renderOptions () {
const {styleOptions, selectedStyle, display} = this.props;
const values = getElementStyleValues(
styleOptions.defaults,
selectedStyle.options,
selectedStyle.displayOptions,
display
);
return (
);
}
}
================================================
FILE: lib/shared/screens/admin/shared/components/page-builder-menu/tabs/style/style-picker/edit.less
================================================
.root {
padding: 20px 14px;
}
================================================
FILE: lib/shared/screens/admin/shared/components/page-builder-menu/tabs/style/style-picker/entry.jsx
================================================
import cx from 'classnames';
import Component from 'components/component';
import OptionsMenu from 'components/options-menu';
import React, {PropTypes} from 'react';
import styles from './entry.less';
export default class Entry extends Component {
static propTypes = {
entry: PropTypes.object.isRequired,
onClick: PropTypes.func.isRequired,
styleOptions: PropTypes.object.isRequired,
removeStyle: PropTypes.func.isRequired,
duplicateStyle: PropTypes.func.isRequired
};
getInitState () {
return {
options: false
};
}
openOptions (event) {
event.preventDefault();
event.stopPropagation();
this.setState({
options: true
});
}
onMouseLeave () {
if (this.state.options) {
this.setState({
options: false
});
}
}
onClick (event) {
event.preventDefault();
this.props.onClick(this.props.entry._id);
}
duplicate () {
this.props.duplicateStyle(this.props.entry);
}
remove () {
this.props.removeStyle(this.props.entry._id);
}
render () {
return (
{this.props.entry.title}
{this.renderOptionsButton()}
{this.renderInfo()}
);
}
renderOptionsButton () {
if (this.props.entry._id !== 'no_style') {
return (
{this.renderOptionsMenu()}
);
}
}
renderOptionsMenu () {
if (this.state.options) {
return (
);
}
}
renderInfo () {
const {styleOptions, entry} = this.props;
if (styleOptions.getIdentifierLabel && entry._id !== 'no_style') {
return (
{styleOptions.getIdentifierLabel(Object.assign({}, styleOptions.defaults, entry.options))}
);
}
}
}
================================================
FILE: lib/shared/screens/admin/shared/components/page-builder-menu/tabs/style/style-picker/entry.less
================================================
@import '~styles/colors.less';
.root {
position: relative;
cursor: pointer;
padding: 0px 11px;
border-bottom: 1px solid @chromeBordersColor;
&:hover, &.selected {
background-color: @chromeBackgroundActive;
}
}
.holder {
display: table;
width: 100%;
table-layout: fixed;
}
.column {
display: table-cell;
vertical-align: top;
}
.title {
font-size: 10px;
text-transform: uppercase;
color: @chromeTextColor;
line-height: 60px;
}
.info {
font-size: 9px;
color: @chromeTextColor;
float: right;
line-height: 60px;
}
.optionsButton {
float: right;
position: relative;
margin-top: 10px;
margin-left: 10px;
text-align: center;
width: 25px;
cursor: pointer;
.icon {
color: @chromeTextColor;
font-size: 12px;
line-height: 40px;
}
&:hover .icon {
color: @primary;
}
}
================================================
FILE: lib/shared/screens/admin/shared/components/page-builder-menu/tabs/style/style-picker/index.js
================================================
import * as pageBuilderActions from 'actions/page-builder';
import * as stylesActions from 'actions/styles';
import debounce from 'lodash.debounce';
import filter from 'lodash.filter';
import find from 'lodash.find';
import forEach from 'lodash.foreach';
import omit from 'lodash.omit';
import Component from 'components/component';
import React, {PropTypes} from 'react';
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
import StylePicker from './style-picker';
@connect(
(state) => ({
styles: state.styles.data,
display: state.display,
selectedElement: state.pageBuilder.selectedElement,
selectedId: state.pageBuilder.selectedId,
elements: state.pageBuilder.elements
}),
(dispatch) => ({
pageBuilderActions: bindActionCreators(pageBuilderActions, dispatch),
...bindActionCreators(stylesActions, dispatch)
})
)
export default class StylePickerContainer extends Component {
static fragments = StylePicker.fragments;
static propTypes = {
style: PropTypes.string.isRequired,
value: PropTypes.string.isRequired,
styles: PropTypes.array.isRequired,
pageBuilderActions: PropTypes.object.isRequired,
saveStyle: PropTypes.func.isRequired,
duplicateStyle: PropTypes.func.isRequired,
updateStyle: PropTypes.func.isRequired,
changeStyleProp: PropTypes.func.isRequired,
// page builder
selectedElement: PropTypes.object.isRequired,
selectedId: PropTypes.string.isRequired,
elements: PropTypes.object.isRequired
};
getInitState () {
const styleOptions = this.getStyleOptions(this.props.style);
this.updateStyle = debounce(::this.onUpdateStyle, 3000);
return {
editing: true,
editingTitle: false,
titleValue: '',
styleOptions
};
}
componentWillReceiveProps (nextProps) {
if (nextProps.style !== this.props.style) {
this.setState({
styleOptions: this.getStyleOptions(nextProps.style)
});
}
}
onUpdateStyle (styleId) {
if (styleId !== 'no_style') {
const selectedStyle = find(this.props.styles, {_id: styleId});
if (selectedStyle) {
this.props.updateStyle(this.constructor.fragments, selectedStyle);
}
}
}
async saveStyle () {
const selectedStyle = this.props.value || 'no_style';
if (selectedStyle === 'no_style') {
const {selectedElement} = this.props;
const style = {
title: this.state.titleValue,
type: this.state.styleOptions.type,
options: selectedElement.style || {},
displayOptions: selectedElement.displayStyle || {}
};
await this.props.saveStyle(this.constructor.fragments, selectedElement.id, style);
this.setState({
editingTitle: false,
titleValue: ''
});
}
}
async duplicateStyle (data) {
const {selectedElement} = this.props;
const style = omit(data, '_id');
style.title += ' copy';
await this.props.saveStyle(this.constructor.fragments, selectedElement.id, style);
this.setState({
editing: true
});
}
onChangeValue (key, value) {
const selectedStyle = this.props.value || 'no_style';
if (selectedStyle === 'no_style') {
const {selectedId} = this.props;
const {changeElementStyle} = this.props.pageBuilderActions;
changeElementStyle(selectedId, key, value);
} else {
this.props.changeStyleProp(selectedStyle, key, value);
this.updateStyle(selectedStyle);
}
}
onChange (value) {
const {selectedId} = this.props;
const {changeElementProperty} = this.props.pageBuilderActions;
changeElementProperty(selectedId, 'style', value);
this.setState({
editing: true
});
}
toggleEditingTitle () {
this.setState({
editingTitle: !this.state.editingTitle
});
}
changeTitleValue (value) {
this.setState({
titleValue: value
});
}
toggleEditing () {
this.setState({
editing: !this.state.editing
});
}
getStyleOptions (style) {
let result = style;
if (typeof style === 'string') {
forEach(this.props.elements, (element) => {
if (element.style && typeof element.style === 'object' && element.style.type === style) {
result = element.style;
}
});
}
return result;
}
render () {
let styles = [];
if (this.state.styleOptions && this.state.styleOptions.type) {
const {selectedElement} = this.props;
styles = filter(this.props.styles, {type: this.state.styleOptions.type});
styles.unshift({
_id: 'no_style',
title: 'No style',
options: selectedElement.style || {},
displayOptions: selectedElement.displayStyle || {}
});
}
let selectedStyle = find(styles, {_id: this.props.value || 'no_style'});
if (!selectedStyle) {
selectedStyle = find(styles, {_id: 'no_style'});
}
return (
);
}
}
================================================
FILE: lib/shared/screens/admin/shared/components/page-builder-menu/tabs/style/style-picker/style-picker.jsx
================================================
import cx from 'classnames';
import Animate from 'components/animate';
import Button from 'components/button';
import Component from 'components/component';
import Input from 'components/input-options/input';
import Scrollable from 'components/scrollable';
import React, {PropTypes} from 'react';
import classes from './style-picker.less';
import Edit from './edit';
import Entry from './entry';
export default class StylePicker extends Component {
static fragments = {
style: {
_id: 1,
type: 1,
title: 1,
options: 1,
displayOptions: 1
}
};
static propTypes = {
value: PropTypes.any.isRequired,
onChange: PropTypes.func.isRequired,
onChangeValue: PropTypes.func.isRequired,
styles: PropTypes.array.isRequired,
selectedStyle: PropTypes.object.isRequired,
editing: PropTypes.bool.isRequired,
editingTitle: PropTypes.bool.isRequired,
titleValue: PropTypes.string.isRequired,
changeTitleValue: PropTypes.func.isRequired,
toggleEditing: PropTypes.func.isRequired,
toggleEditingTitle: PropTypes.func.isRequired,
saveStyle: PropTypes.func.isRequired,
styleOptions: PropTypes.object.isRequired,
removeStyle: PropTypes.func.isRequired,
duplicateStyle: PropTypes.func.isRequired,
display: React.PropTypes.string.isRequired
};
onSubmit (event) {
event.preventDefault();
this.props.saveStyle();
}
render () {
const {selectedStyle, toggleEditing, editing} = this.props;
return (
{selectedStyle.title}
{this.renderContent()}
{this.renderSaveStyle()}
);
}
renderContent () {
let result;
if (this.props.editing) {
result = (
{this.renderEdit()}
);
} else if (this.props.styles.length > 1) {
result = (
{this.props.styles.map(this.renderEntry, this)}
);
}
return result;
}
renderEntry (entry) {
if (entry._id !== this.props.selectedStyle._id) {
return (
);
}
}
renderEdit () {
return (
);
}
renderSaveStyle () {
const {
editing,
selectedStyle,
editingTitle,
titleValue,
changeTitleValue,
saveStyle,
toggleEditingTitle
} = this.props;
if (editing && selectedStyle._id === 'no_style') {
let result;
if (editingTitle) {
result = (
);
} else {
result = (
);
}
return result;
}
}
}
================================================
FILE: lib/shared/screens/admin/shared/components/page-builder-menu/tabs/style/style-picker/style-picker.less
================================================
@import '~styles/colors.less';
.selected {
position: relative;
padding: 0 18px;
border-bottom: 1px solid @chromeBordersColor;
cursor: pointer;
:global {
span, i {
display: inline-block;
}
span {
font-size: 11px;
text-transform: uppercase;
line-height: 65px;
color: @chromeTextColor;
}
i {
font-size: 14px;
position: absolute;
right: 12px;
line-height: 65px;
color: @chromeTextSubColor;
}
}
&:hover {
background-color: @chromeBackgroundActive;
}
}
.content {
position: absolute;
left: 0;
right: 0;
bottom: 0;
top: 66px;
}
.noStyle {
bottom: 91px;
}
.saveStyle {
position: absolute;
bottom: 0px;
height: 60px;
left: 0; right: 0;
border-top: 1px solid @chromeBordersColor;
text-align: center;
padding: 10px;
.input {
width: 205px;
height: 39px;
display: inline-block;
vertical-align: top;
:global input {
text-transform: uppercase;
}
}
}
.submitButton {
display: inline-block;
vertical-align: top;
width: 50px;
text-align: center;
cursor: pointer;
:global i {
font-size: 18px;
line-height: 39px;
color: @chromeTextSubColor;
transition: all 0.3s ease-out;
}
&:hover :global i {
color: @chromeTextColor;
transform: translateX(5px);
}
}
================================================
FILE: lib/shared/screens/admin/shared/components/page-builder-menu/tabs/style/style.jsx
================================================
import getElementProps from 'helpers/get-element-props';
import Component from 'components/component';
import React, {PropTypes} from 'react';
import styles from './style.less';
import StylePicker from './style-picker';
export default class Style extends Component {
static propTypes = {
selectedElement: PropTypes.object,
elements: PropTypes.object,
display: PropTypes.string,
styles: PropTypes.array.isRequired
};
render () {
const {selectedElement, elements} = this.props;
let result;
if (selectedElement && selectedElement.id !== 'body') {
const Element = elements[selectedElement.tag];
if (Element && Element.style) {
result = this.renderStylePicker(Element);
} else {
result = this.renderNotStylable();
}
} else {
result = this.renderNoneSelected();
}
return result;
}
renderStylePicker (Element) {
const {selectedElement, display} = this.props;
const elementProps = getElementProps(selectedElement, display);
return (
);
}
renderNoneSelected () {
return (
Relax, you have to select an element first!
);
}
renderNotStylable () {
return (
Current selected element has no style options.
);
}
}
================================================
FILE: lib/shared/screens/admin/shared/components/page-builder-menu/tabs/style/style.less
================================================
@import '~styles/colors.less';
.info {
position: absolute;
left: 0;
right: 0;
top: 40%;
transform: translateY(-50%);
text-align: center;
padding: 0 45px;
:global i {
font-size: 35px;
color: @chromeTextSubColor;
margin-bottom: 15px;
}
}
.label {
font-size: 12px;
color: @chromeTextSubColor;
}
================================================
FILE: lib/shared/screens/admin/shared/components/page-builder-menu/tabs/tab-button.jsx
================================================
import cx from 'classnames';
import Component from 'components/component';
import React, {PropTypes} from 'react';
import styles from './tab-button.less';
export default class TabButton extends Component {
static propTypes = {
tab: PropTypes.string.isRequired,
onClick: PropTypes.func.isRequired,
active: PropTypes.bool.isRequired
};
onClick () {
const {onClick, tab} = this.props;
onClick(tab);
}
render () {
const {tab, active} = this.props;
return (
);
}
}
================================================
FILE: lib/shared/screens/admin/shared/components/page-builder-menu/tabs/tab-button.less
================================================
@import '~styles/colors.less';
.root {
display: inline-block;
vertical-align: top;
width: 33.3332%;
color: @chromeTextColor;
text-align: center;
font-size: 12px;
line-height: 49px;
text-transform: uppercase;
border-right: 1px solid @chromeBordersColor;
&:last-child {
border-right: 0;
}
}
.selected {
background-color: @chromeBackgroundActive;
}
================================================
FILE: lib/shared/screens/admin/shared/components/page-builder-menu/tabs/tabs.jsx
================================================
import Component from 'components/component';
import React, {PropTypes} from 'react';
import styles from './tabs.less';
import Layers from './layers';
import Settings from './settings';
import Style from './style';
import TabButton from './tab-button';
export default class Tabs extends Component {
static propTypes = {
menuTab: PropTypes.string.isRequired,
setMenuTab: PropTypes.func.isRequired
};
render () {
const {menuTab} = this.props;
return (
);
}
renderContent () {
const {menuTab} = this.props;
let result;
if (menuTab === 'style') {
result = ;
} else if (menuTab === 'settings') {
result = ;
} else if (menuTab === 'layers') {
result = ;
}
return result;
}
}
================================================
FILE: lib/shared/screens/admin/shared/components/page-builder-menu/tabs/tabs.less
================================================
@import '~styles/colors.less';
.tabs {
height: 50px;
border-bottom: 1px solid @chromeBordersColor;
}
.content {
position: absolute;
top: 50px;
left: 0;
right: 0;
bottom: 0;
}
================================================
FILE: lib/shared/screens/admin/shared/components/scrollable/index.jsx
================================================
import cx from 'classnames';
import Component from 'components/component';
import GeminiScrollbar from 'react-gemini-scrollbar';
import React, {PropTypes} from 'react';
import styles from './index.less';
export default class Scrollable extends Component {
static propTypes = {
children: PropTypes.node,
autoshow: PropTypes.bool,
className: PropTypes.string
};
static defaultProps = {
autoshow: true
};
render () {
return (
{this.props.children}
);
}
}
================================================
FILE: lib/shared/screens/admin/shared/components/scrollable/index.less
================================================
.root {
position: absolute;
top: 0; left: 0; bottom: 0; right: 0;
}
.scrollbar {
:global {
/* @helper: used to measure the scrollbar width on a temporal element */
.gm-test {
width: 100px;
height: 100px;
position: absolute;
top: -9999px;
overflow: scroll;
-ms-overflow-style: scrollbar;
}
/* disable selection while dragging */
.gm-scrollbar-disable-selection {
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
/* fallback for native floating scrollbars */
.gm-prevented {
-webkit-overflow-scrolling: touch;
}
.gm-prevented .gm-scrollbar {
display: none;
}
/* actual gemini-scrollbar styles */
.gm-scrollbar-container {
position: relative;
overflow: hidden!important;
width: 100%;
height: 100%;
}
.gm-scrollbar {
position: absolute;
right: 2px;
bottom: 2px;
z-index: 1;
border-radius: 3px;
}
.gm-scrollbar.-vertical {
width: 5px;
top: 2px;
}
.gm-scrollbar.-horizontal {
height: 6px;
left: 2px;
}
.gm-scrollbar .thumb {
position: relative;
display: block;
width: 0;
height: 0;
cursor: pointer;
border-radius: inherit;
background-color: rgba(0, 0, 0, 0.1);
}
.gm-scrollbar .thumb:hover,
.gm-scrollbar .thumb:active {
background-color: rgba(0, 0, 0, 0.4);
}
.gm-scrollbar.-vertical .thumb {
width: 100%;
}
.gm-scrollbar.-horizontal .thumb {
height: 100%;
}
.gm-scrollbar-container .gm-scroll-view {
width: 100%;
height: 100%;
overflow: scroll;
-webkit-overflow-scrolling: touch;
}
/* @option: autoshow */
.gm-scrollbar-container.gm-autoshow .gm-scrollbar {
opacity: 0;
transition: opacity 120ms ease-out;
}
.gm-scrollbar-container.gm-autoshow:hover .gm-scrollbar,
.gm-scrollbar-container.gm-autoshow:focus .gm-scrollbar {
opacity: 1;
transition: opacity 340ms ease-out;
}
}
}
================================================
FILE: lib/shared/screens/admin/shared/components/spinner/index.jsx
================================================
import cx from 'classnames';
import Component from 'components/component';
import React, {PropTypes} from 'react';
import styles from './index.less';
export default class Spinner extends Component {
static propTypes = {
className: PropTypes.string,
small: PropTypes.bool
};
render () {
const {className, small} = this.props;
return (
);
}
}
================================================
FILE: lib/shared/screens/admin/shared/components/spinner/index.less
================================================
// Bar spinner (check)
.spinner {
display: inline-block;
font-size: 10px;
position: relative;
text-indent: -9999em;
border-top: 3px solid #3EB0F2;
border-right: 3px solid #DADADA;
border-bottom: 3px solid #DADADA;
border-left: 3px solid #DADADA;
-webkit-transform: translateZ(0);
-ms-transform: translateZ(0);
transform: translateZ(0);
-webkit-animation: sk-bar-spinner-frames 1.1s infinite linear;
animation: sk-bar-spinner-frames 1.1s infinite linear;
}
.spinner, .spinner:after {
border-radius: 50%;
width: 20px;
height: 20px;
}
.small, .small:after {
width: 13px;
height: 13px;
}
@-webkit-keyframes sk-bar-spinner-frames {.sk-bar-spinner-frames;}
@keyframes sk-bar-spinner-frames {.sk-bar-spinner-frames;}
.sk-bar-spinner-frames() {
0% {
-webkit-transform: rotate(0deg);
transform: rotate(0deg);
}
100% {
-webkit-transform: rotate(360deg);
transform: rotate(360deg);
}
}
================================================
FILE: lib/shared/screens/admin/shared/components/stick/index.jsx
================================================
import cx from 'classnames';
import Animate from 'components/animate';
import Component from 'components/component';
import React, {PropTypes} from 'react';
import styles from './index.less';
export default class Stick extends Component {
static propTypes = {
element: PropTypes.node.isRequired,
children: PropTypes.node.isRequired,
className: PropTypes.string,
transition: PropTypes.string,
verticalPosition: PropTypes.oneOf(['top', 'center', 'bottom']),
horizontalPosition: PropTypes.oneOf(['left', 'center', 'right']),
verticalOffset: PropTypes.number,
horizontalOffset: PropTypes.number,
onClose: PropTypes.func
};
static defaultProps = {
transition: 'fadeIn',
verticalPosition: 'top',
horizontalPosition: 'center',
verticalOffset: 0,
horizontalOffset: 0
};
getInitState () {
return {
left: 0,
top: 0
};
}
componentDidMount () {
this.mousewheelevt = (/Firefox/i.test(navigator.userAgent)) ? 'DOMMouseScroll' : 'mousewheel';
this.scrollBind = ::this.onScroll;
this.resizeBind = ::this.onResize;
this.onCloseBind = ::this.onClose;
document.body.addEventListener(this.mousewheelevt, this.scrollBind, false);
window.addEventListener('resize', this.resizeBind, false);
this.props.onClose && document.body.addEventListener('mousedown', this.onCloseBind, false);
this.updatePosition();
}
componentWillUnmount () {
document.body.removeEventListener(this.mousewheelevt, this.scrollBind);
window.removeEventListener('resize', this.resizeBind);
this.props.onClose && document.body.removeEventListener('mousedown', this.onCloseBind);
}
onClose (event) {
const rect = this.props.element.getBoundingClientRect();
const thisRect = this.refs.holder.getBoundingClientRect();
const outOfElement = (event.pageX < rect.left || event.pageX > rect.left + rect.width) ||
(event.pageY < rect.top || event.pageY > rect.top + rect.height);
const outOfThis = (event.pageX < thisRect.left || event.pageX > thisRect.left + thisRect.width) ||
(event.pageY < thisRect.top || event.pageY > thisRect.top + thisRect.height);
if (outOfElement && outOfThis) {
this.props.onClose();
}
}
onScroll () {
this.updatePosition();
this.updateTimeout = setTimeout(::this.updatePosition, 0);
}
onResize () {
this.updatePosition();
this.updateTimeout = setTimeout(::this.updatePosition, 10);
}
updatePosition () {
this.setState(this.getPosition());
}
getPosition () {
const position = {
left: 0,
top: 0,
horizontal: this.props.horizontalPosition,
vertical: this.props.verticalPosition
};
const rect = this.props.element.getBoundingClientRect();
const thisRect = this.refs.holder.getBoundingClientRect();
const windowHeight = window.innerHeight;
const windowWidth = window.innerWidth;
switch (this.props.horizontalPosition) {
case 'left':
position.left = rect.left;
break;
case 'center':
position.left = rect.left + rect.width / 2 - thisRect.width / 2;
break;
case 'right':
position.left = rect.left + rect.width - thisRect.width;
break;
default:
position.left = 0;
}
position.left += this.props.horizontalOffset;
switch (this.props.verticalPosition) {
case 'top':
position.top = rect.top - thisRect.height;
break;
case 'center':
position.top = rect.top + rect.height / 2 - thisRect.height / 2;
break;
case 'bottom':
position.top = rect.top + rect.height;
break;
default:
position.top = 0;
}
position.top += this.props.verticalOffset;
// Overflows
if (position.top + thisRect.height > windowHeight) {
position.top = rect.top - thisRect.height - this.props.verticalOffset;
position.vertical = 'top';
}
if (position.left + thisRect.width > windowWidth) {
position.left = rect.left + rect.width - thisRect.width - this.props.horizontalOffset;
position.horizontal = 'right';
}
return position;
}
render () {
const {transition, className} = this.props;
// const {vertical, horizontal} = this.state;
const style = {
left: this.state.left,
top: this.state.top
};
return (
{this.props.children}
);
}
}
================================================
FILE: lib/shared/screens/admin/shared/components/stick/index.less
================================================
.stick {
position: absolute;
top: 0; left: 0;
display: inline-block;
z-index: 11;
}
================================================
FILE: lib/shared/screens/admin/shared/components/upload/index.jsx
================================================
import cx from 'classnames';
import Component from 'components/component';
import ReactDropzone from 'react-dropzone';
import React, {PropTypes} from 'react';
import styles from './index.less';
export default class Upload extends Component {
static propTypes = {
accept: PropTypes.string,
clickable: PropTypes.bool,
children: PropTypes.node,
onFiles: PropTypes.func.isRequired,
className: PropTypes.string,
activeClassName: PropTypes.string,
rejectClassName: PropTypes.string,
showInfos: PropTypes.bool
};
static defaultProps = {
accept: 'image/*,video/*,audio/*',
clickable: true,
droppable: true,
showInfos: true
};
onDrop (files) {
this.props.onFiles(files);
// files.forEach((file) => {
// const reader = new FileReader();
// reader.onload = (event) => {
// this.props.onFile({
// file: event.target.result,
// filename: file.name
// }, file);
// };
// reader.readAsDataURL(file);
// });
}
render () {
const {className, activeClassName, rejectClassName} = this.props;
return (
{this.renderInfos()}
{this.props.children}
);
}
renderInfos () {
if (this.props.showInfos) {
return (
Release your file(s) to upload
You're dragging invalid files
);
}
}
}
================================================
FILE: lib/shared/screens/admin/shared/components/upload/index.less
================================================
@import '~styles/colors.less';
.canDrop, .cannotDrop {
visibility: hidden;
position: absolute;
top: 10px;
left: 50%;
transform: translateX(-50%);
border-radius: 3px;
pointer-events: none;
border: 1px solid @primary;
background-color: rgba(17, 164, 255, 0.75);
color: #ffffff;
font-size: 12px;
line-height: 28px;
text-align: center;
width: 280px;
z-index: 1;
}
.cannotDrop {
border-color: #F55151;
background-color: rgba(255, 17, 17, 0.75);
}
.active {
.canDrop {
visibility: visible;
}
&:after {
content: ' ';
position: absolute;
top: 0; right: 0; bottom: 0; left: 0;
border: 5px solid @primary;
}
}
.reject {
.cannotDrop {
visibility: visible;
}
&:after {
content: ' ';
position: absolute;
top: 0; right: 0; bottom: 0; left: 0;
border: 5px solid #F55151;
}
}
================================================
FILE: lib/shared/screens/admin/shared/helpers/input-options-map.js
================================================
import BorderPicker from 'components/input-options/border';
import BorderStyle from 'components/input-options/border-style';
import BoxShadow from 'components/input-options/box-shadow';
import Button from 'components/input-options/button';
import Checkbox from 'components/input-options/checkbox';
import ColorPicker from 'components/input-options/color';
import ColumnsManager from 'components/input-options/columns';
import Combobox from 'components/input-options/combobox';
import CornersPicker from 'components/input-options/corners';
import Filters from 'components/input-options/filters';
import FontPicker from 'components/input-options/font';
import HtmlArea from 'components/input-options/rich-text';
import IconPicker from 'components/input-options/icon';
import ImagePicker from 'components/input-options/image';
import Input from 'components/input-options/input';
import MenuPicker from 'components/input-options/menu';
import NumberInput from 'components/input-options/number';
import Optional from 'components/input-options/optional';
import PagePicker from 'components/input-options/page';
import SchemaPicker from 'components/input-options/schema';
import Section from 'components/input-options/section';
import ShadowPosition from 'components/input-options/shadow-position';
import Sorts from 'components/input-options/sorts';
import SpacingPicker from 'components/input-options/spacing';
import TextShadow from 'components/input-options/text-shadow';
export const TypesOptionsMap = {
Color: ColorPicker,
String: Input,
Image: ImagePicker,
Audio: ImagePicker,
Select: Combobox,
SchemaPicker,
MenuPicker,
PagePicker,
Number: NumberInput,
Pixels: NumberInput,
Percentage: NumberInput,
Padding: SpacingPicker,
Margin: SpacingPicker,
Boolean: Checkbox,
Font: FontPicker,
Button,
Icon: IconPicker,
Corners: CornersPicker,
ManageColumns: ColumnsManager,
Border: BorderPicker,
LineStyle: BorderStyle,
Optional,
Section,
Html: HtmlArea,
Filters,
Sorts,
TextShadow,
BoxShadow,
ShadowPosition,
Date: NumberInput
};
export const TypesOptionsDefaultProps = {
Pixels: {
allowed: ['px']
},
Percentage: {
min: 0,
max: 100,
allowed: ['%']
},
Padding: {
type: 'padding'
},
Margin: {
type: 'margin'
},
Audio: {
width: 50,
height: 50,
type: 'audio'
}
};
================================================
FILE: lib/shared/screens/auth/components/logo/index.jsx
================================================
import React from 'react';
import Component from 'components/component';
import styles from './index.less';
export default class Logo extends Component {
render () {
return (
beta
);
}
}
================================================
FILE: lib/shared/screens/auth/components/logo/index.less
================================================
@import '~styles/colors.less';
.logo {
text-align: center;
position: relative;
display: inline-block;
margin-bottom: 30px;
}
.version {
position: absolute;
background-color: @primary;
right: -10px;
top: 0;
display: inline-block;
font-size: 9px;
color: #ffffff;
border-radius: 4px;
padding: 1px 4px;
}
================================================
FILE: lib/shared/screens/auth/index.css
================================================
.content {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
max-width: 100%;
width: 400px;
text-align: center;
font-family: 'Open Sans';
}
================================================
FILE: lib/shared/screens/auth/index.jsx
================================================
import 'styles/normalize.less';
import 'styles/nucleo/index.less';
import React, {PropTypes} from 'react';
import Component from 'components/component';
import styles from './index.css';
import Logo from './components/logo';
export default class Auth extends Component {
static propTypes = {
children: PropTypes.node.isRequired
};
render () {
return (
{this.props.children}
);
}
}
================================================
FILE: lib/shared/screens/auth/screens/init/components/init.jsx
================================================
import authStyles from 'styles/auth.less';
import Button from 'components/button';
import React, {PropTypes} from 'react';
import Component from 'components/component';
export default class Init extends Component {
static propTypes = {
onChange: PropTypes.func.isRequired,
onSubmit: PropTypes.func.isRequired,
user: PropTypes.object,
error: PropTypes.string
};
onChange (id, event) {
this.props.onChange(id, event.target.value);
}
render () {
const {username, password, name, email} = this.props.user;
return (
Welcome to Relax!
Register the first user to start building your website in a breeze.
);
}
}
================================================
FILE: lib/shared/screens/auth/screens/init/index.js
================================================
import Component from 'components/component';
import React, {PropTypes} from 'react';
import {addUser} from 'actions/users';
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
import AdminInit from './components/init';
@connect(
() => ({}),
(dispatch) => bindActionCreators({addUser}, dispatch)
)
export default class Init extends Component {
static propTypes = {
addUser: PropTypes.func.isRequired
};
getInitState () {
return {
user: {
username: '',
name: '',
password: '',
email: ''
}
};
}
onChange (id, value) {
this.state.user[id] = value;
this.setState({
user: this.state.user
});
}
onSubmit (event) {
event.preventDefault();
this.props
.addUser({
users: {
_id: 1
}
}, {
...this.state.user
})
.then(() => {
window.location = '/admin/login';
})
.done();
}
render () {
return (
);
}
}
================================================
FILE: lib/shared/screens/auth/screens/login/components/login.jsx
================================================
import authStyles from 'styles/auth.less';
import Button from 'components/button';
import React, {PropTypes} from 'react';
import Component from 'components/component';
export default class Login extends Component {
static propTypes = {
onSubmit: PropTypes.func.isRequired,
fieldChange: PropTypes.func.isRequired,
username: PropTypes.string.isRequired,
password: PropTypes.string.isRequired,
error: PropTypes.string
};
onChange (id, event) {
this.props.fieldChange(id, event.target.value);
}
render () {
return (
Welcome back!
Login with your account below to get started
);
}
}
================================================
FILE: lib/shared/screens/auth/screens/login/index.js
================================================
import request from 'superagent';
import React, {PropTypes} from 'react';
import Component from 'components/component';
import Login from './components/login';
export default class LoginContainer extends Component {
static propTypes = {
history: PropTypes.object.isRequired
};
getInitState () {
return {
username: '',
password: ''
};
}
onSubmit (event) {
event.preventDefault();
const {username, password} = this.state;
request
.post('/admin/login')
.set('Content-Type', 'application/json')
.set('Accept', 'application/json')
.send({username, password})
.end((error, res) => {
if (error) {
this.setState({
error: res.body.message
});
} else {
window.location.href = '/admin';
}
});
}
fieldChange (id, value) {
this.setState({
[id]: value
});
}
render () {
return (
);
}
}
================================================
FILE: lib/shared/screens/auth/shared/styles/auth.less
================================================
@import '~styles/colors.less';
.title {
font-size: 34px;
line-height: 44px;
font-weight: 400;
margin: 0;
color: #454545;
}
.subTitle {
font-size: 14px;
line-height: 20px;
font-weight: 300;
margin: 0;
color: #454545;
}
.form {
margin-top: 40px;
text-align: left;
width: 334px;
max-width: 100%;
display: inline-block;
:global {
label {
display: block;
margin-bottom: 21px;
border-bottom: 1px solid #e1e1e1;
padding: 0px 7px;
input {
font-size: 13px;
border: 0;
outline: 0;
font-weight: 300;
color: #454545;
width: 280px;
&::-webkit-input-placeholder {
color: #9b9b9b;
}
&:-moz-placeholder {
color: #9b9b9b;
}
&::-moz-placeholder {
color: #9b9b9b;
}
&:-ms-input-placeholder {
color: #9b9b9b;
}
&::-ms-input-placeholder {
color: #9b9b9b;
}
&:placeholder-shown {
color: #9b9b9b;
}
&:focus {
border-color: @primary;
}
}
i, input {
display: inline-block;
vertical-align: top;
line-height: 35px;
color: #9b9b9b;
}
i {
width: 20px;
text-align: center;
font-size: 16px;
margin-right: 18px;
}
}
}
}
.error {
font-size: 11px;
color: @alert;
margin-top: 10px;
text-align: center;
}
================================================
FILE: lib/shared/styles/colors.less
================================================
@primary: #12A5FF;
@primaryLight: lighten(@primary, 20%);
@primaryDarker: darken(@primary, 20%);
@primarySub: #73CAFF;
@primaryBack: #DFF3FF;
@alert: #ff0000;
@success: #17B923;
@draft: #F7CF00;
@chromeBackgroundColor: #33363b;
@chromeBackgroundDarkerColor: #2c2f34;
@chromeBackgroundActive: #3f4249;
@chromeBordersColor: #494949;
@chromeTextColor: #efefef;
@chromeTextSubColor: #999999;
@chromeTextColorHighlight: #ffffff;
@dropSuccess: #7ED321;
@dropSuccessBack: fade(#7ED321, 10%);
@adminBorders: #efefef;
@adminInputBorders: #cccccc;
@adminText: #999999;
@adminTextSub: #dbdbdb;
@adminTextHighlight: #3a3a3a;
@adminBordersInputs: #E1E1E1;
================================================
FILE: lib/shared/styles/element.less
================================================
.editingWrapper {
position: relative;
.editingCover {
position: absolute;
top: 0; left: 0; right: 0; bottom: 0;
background-color: rgba(255, 255, 255, 0);
transition: all 0.2s ease-out;
}
&:hover .editingCover {
background-color: rgba(255, 255, 255, 0.2);
}
}
.dummy {
background-color: #efefef;
height: 200px;
text-align: center;
max-height: 100%;
:global i {
font-size: 25px;
color: #333333;
top: 50%;
position: relative;
transform: translateY(-50%);
}
}
================================================
FILE: lib/shared/styles/normalize.less
================================================
/*! normalize.css v3.0.2 | MIT License | git.io/normalize */
/**
* 1. Set default font family to sans-serif.
* 2. Prevent iOS text size adjust after orientation change, without disabling
* user zoom.
*/
html {
font-family: sans-serif; /* 1 */
-ms-text-size-adjust: 100%; /* 2 */
-webkit-text-size-adjust: 100%; /* 2 */
}
form {
margin: 0;
}
// Reset button tag style
button {
outline: 0;
background: transparent;
border: 0;
padding: 0;
}
iframe {
border: 0;
}
/**
* Remove default margin.
*/
body {
margin: 0;
}
* {
box-sizing: border-box;
}
/* HTML5 display definitions
========================================================================== */
/**
* Correct `block` display not defined for any HTML5 element in IE 8/9.
* Correct `block` display not defined for `details` or `summary` in IE 10/11
* and Firefox.
* Correct `block` display not defined for `main` in IE 11.
*/
article,
aside,
details,
figcaption,
figure,
footer,
header,
hgroup,
main,
menu,
nav,
section,
summary {
display: block;
}
/**
* 1. Correct `inline-block` display not defined in IE 8/9.
* 2. Normalize vertical alignment of `progress` in Chrome, Firefox, and Opera.
*/
audio,
canvas,
progress,
video {
display: inline-block; /* 1 */
vertical-align: baseline; /* 2 */
}
/**
* Prevent modern browsers from displaying `audio` without controls.
* Remove excess height in iOS 5 devices.
*/
audio:not([controls]) {
display: none;
height: 0;
}
/**
* Address `[hidden]` styling not present in IE 8/9/10.
* Hide the `template` element in IE 8/9/11, Safari, and Firefox < 22.
*/
[hidden],
template {
display: none;
}
/* Links
========================================================================== */
/**
* Remove the gray background color from active links in IE 10.
*/
a {
background-color: transparent;
}
/**
* Improve readability when focused and also mouse hovered in all browsers.
*/
a:active,
a:hover {
outline: 0;
}
/* Text-level semantics
========================================================================== */
/**
* Address styling not present in IE 8/9/10/11, Safari, and Chrome.
*/
abbr[title] {
border-bottom: 1px dotted;
}
/**
* Address style set to `bolder` in Firefox 4+, Safari, and Chrome.
*/
b,
strong {
font-weight: bold;
}
/**
* Address styling not present in Safari and Chrome.
*/
dfn {
font-style: italic;
}
/**
* Address variable `h1` font-size and margin within `section` and `article`
* contexts in Firefox 4+, Safari, and Chrome.
*/
h1 {
font-size: 2em;
margin: 0.67em 0;
}
/**
* Address styling not present in IE 8/9.
*/
mark {
background: #ff0;
color: #000;
}
/**
* Address inconsistent and variable font size in all browsers.
*/
small {
font-size: 80%;
}
/**
* Prevent `sub` and `sup` affecting `line-height` in all browsers.
*/
sub,
sup {
font-size: 75%;
line-height: 0;
position: relative;
vertical-align: baseline;
}
sup {
top: -0.5em;
}
sub {
bottom: -0.25em;
}
/* Embedded content
========================================================================== */
/**
* Remove border when inside `a` element in IE 8/9/10.
*/
img {
border: 0;
}
/**
* Correct overflow not hidden in IE 9/10/11.
*/
svg:not(:root) {
overflow: hidden;
}
/* Grouping content
========================================================================== */
/**
* Address margin not present in IE 8/9 and Safari.
*/
figure {
margin: 1em 40px;
}
/**
* Address differences between Firefox and other browsers.
*/
hr {
-moz-box-sizing: content-box;
box-sizing: content-box;
height: 0;
}
/**
* Contain overflow in all browsers.
*/
pre {
overflow: auto;
}
/**
* Address odd `em`-unit font size rendering in all browsers.
*/
code,
kbd,
pre,
samp {
font-family: monospace, monospace;
font-size: 1em;
}
/* Forms
========================================================================== */
/**
* Known limitation: by default, Chrome and Safari on OS X allow very limited
* styling of `select`, unless a `border` property is set.
*/
/**
* 1. Correct color not being inherited.
* Known issue: affects color of disabled elements.
* 2. Correct font properties not being inherited.
* 3. Address margins set differently in Firefox 4+, Safari, and Chrome.
*/
button,
input,
optgroup,
select,
textarea {
color: inherit; /* 1 */
font: inherit; /* 2 */
margin: 0; /* 3 */
}
/**
* Address `overflow` set to `hidden` in IE 8/9/10/11.
*/
button {
overflow: visible;
}
/**
* Address inconsistent `text-transform` inheritance for `button` and `select`.
* All other form control elements do not inherit `text-transform` values.
* Correct `button` style inheritance in Firefox, IE 8/9/10/11, and Opera.
* Correct `select` style inheritance in Firefox.
*/
button,
select {
text-transform: none;
}
/**
* 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio`
* and `video` controls.
* 2. Correct inability to style clickable `input` types in iOS.
* 3. Improve usability and consistency of cursor style between image-type
* `input` and others.
*/
button,
html input[type="button"], /* 1 */
input[type="reset"],
input[type="submit"] {
-webkit-appearance: button; /* 2 */
cursor: pointer; /* 3 */
}
/**
* Re-set default cursor for disabled elements.
*/
button[disabled],
html input[disabled] {
cursor: default;
}
/**
* Remove inner padding and border in Firefox 4+.
*/
button::-moz-focus-inner,
input::-moz-focus-inner {
border: 0;
padding: 0;
}
/**
* Address Firefox 4+ setting `line-height` on `input` using `!important` in
* the UA stylesheet.
*/
input {
line-height: normal;
}
/**
* It's recommended that you don't attempt to style these elements.
* Firefox's implementation doesn't respect box-sizing, padding, or width.
*
* 1. Address box sizing set to `content-box` in IE 8/9/10.
* 2. Remove excess padding in IE 8/9/10.
*/
input[type="checkbox"],
input[type="radio"] {
box-sizing: border-box; /* 1 */
padding: 0; /* 2 */
}
/**
* Fix the cursor style for Chrome's increment/decrement buttons. For certain
* `font-size` values of the `input`, it causes the cursor style of the
* decrement button to change from `default` to `text`.
*/
input[type="number"]::-webkit-inner-spin-button,
input[type="number"]::-webkit-outer-spin-button {
height: auto;
}
/**
* 1. Address `appearance` set to `searchfield` in Safari and Chrome.
* 2. Address `box-sizing` set to `border-box` in Safari and Chrome
* (include `-moz` to future-proof).
*/
input[type="search"] {
-webkit-appearance: textfield; /* 1 */
-moz-box-sizing: content-box;
-webkit-box-sizing: content-box; /* 2 */
box-sizing: content-box;
}
/**
* Remove inner padding and search cancel button in Safari and Chrome on OS X.
* Safari (but not Chrome) clips the cancel button when the search input has
* padding (and `textfield` appearance).
*/
input[type="search"]::-webkit-search-cancel-button,
input[type="search"]::-webkit-search-decoration {
-webkit-appearance: none;
}
/**
* Define consistent border, margin, and padding.
*/
fieldset {
border: 1px solid #c0c0c0;
margin: 0 2px;
padding: 0.35em 0.625em 0.75em;
}
/**
* 1. Correct `color` not being inherited in IE 8/9/10/11.
* 2. Remove padding so people aren't caught out if they zero out fieldsets.
*/
legend {
border: 0; /* 1 */
padding: 0; /* 2 */
}
/**
* Remove default vertical scrollbar in IE 8/9/10/11.
*/
textarea {
overflow: auto;
}
/**
* Don't inherit the `font-weight` (applied by a rule above).
* NOTE: the default cannot safely be changed in Chrome and Safari on OS X.
*/
optgroup {
font-weight: bold;
}
/* Tables
========================================================================== */
/**
* Remove most spacing between table cells.
*/
table {
border-collapse: collapse;
border-spacing: 0;
}
td,
th {
padding: 0;
}
body{
overflow-x: hidden;
}
/*
* Remove text-shadow in selection highlight:
* https://twitter.com/miketaylr/status/12228805301
*
* These selection rule sets have to be separate.
* Customize the background color to match your design.
*/
::-moz-selection {
background: #b3d4fc;
text-shadow: none;
}
::selection {
background: #b3d4fc;
text-shadow: none;
}
/*
* A better looking default horizontal rule
*/
hr {
display: block;
height: 1px;
border: 0;
border-top: 1px solid #ccc;
margin: 1em 0;
padding: 0;
}
/*
* Remove the gap between audio, canvas, iframes,
* images, videos and the bottom of their containers:
* https://github.com/h5bp/html5-boilerplate/issues/440
*/
audio,
canvas,
iframe,
img,
svg,
video {
vertical-align: middle;
}
/*
* Remove default fieldset styles.
*/
fieldset {
border: 0;
margin: 0;
padding: 0;
}
/*
* Allow only vertical resizing of textareas.
*/
textarea {
resize: vertical;
}
================================================
FILE: lib/shared/styles/nucleo/index.less
================================================
@import './outline/less/nucleo-outline.less';
@import './mini/less/nucleo-mini.less';
================================================
FILE: lib/shared/styles/nucleo/mini/less/icons.less
================================================
/*------------------------
font icons
-------------------------*/
.nc-icon-mini.envir_bulb-saver:before {
content: "\ee5c";
}
.nc-icon-mini.envir_bulb:before {
content: "\ee5d";
}
.nc-icon-mini.envir_car:before {
content: "\ee5e";
}
.nc-icon-mini.envir_fuel-electric:before {
content: "\ee5f";
}
.nc-icon-mini.envir_fuel:before {
content: "\ee60";
}
.nc-icon-mini.envir_home:before {
content: "\ee61";
}
.nc-icon-mini.envir_level:before {
content: "\ee62";
}
.nc-icon-mini.envir_panel:before {
content: "\ee63";
}
.nc-icon-mini.envir_radiation:before {
content: "\ee64";
}
.nc-icon-mini.envir_recycling:before {
content: "\ee65";
}
.nc-icon-mini.envir_save-planet:before {
content: "\ee66";
}
.nc-icon-mini.envir_waste-danger:before {
content: "\ee67";
}
.nc-icon-mini.envir_waste-recycling:before {
content: "\ee68";
}
.nc-icon-mini.envir_waste:before {
content: "\ee69";
}
.nc-icon-mini.envir_water-hand:before {
content: "\ee6a";
}
.nc-icon-mini.envir_water-sink:before {
content: "\ee6b";
}
.nc-icon-mini.envir_water:before {
content: "\ee6c";
}
.nc-icon-mini.envir_wind:before {
content: "\ee6d";
}
.nc-icon-mini.arrows-1_back-78:before {
content: "\e900";
}
.nc-icon-mini.arrows-1_back-80:before {
content: "\e901";
}
.nc-icon-mini.arrows-1_bold-down:before {
content: "\e902";
}
.nc-icon-mini.arrows-1_bold-left:before {
content: "\e903";
}
.nc-icon-mini.arrows-1_bold-right:before {
content: "\e904";
}
.nc-icon-mini.arrows-1_bold-up:before {
content: "\e905";
}
.nc-icon-mini.arrows-1_circle-down-12:before {
content: "\e906";
}
.nc-icon-mini.arrows-1_circle-down-40:before {
content: "\e907";
}
.nc-icon-mini.arrows-1_circle-left-10:before {
content: "\e908";
}
.nc-icon-mini.arrows-1_circle-left-38:before {
content: "\e909";
}
.nc-icon-mini.arrows-1_circle-right-09:before {
content: "\e90a";
}
.nc-icon-mini.arrows-1_circle-right-37:before {
content: "\e90b";
}
.nc-icon-mini.arrows-1_circle-up-11:before {
content: "\e90c";
}
.nc-icon-mini.arrows-1_circle-up-39:before {
content: "\e90d";
}
.nc-icon-mini.arrows-1_cloud-download-95:before {
content: "\e90e";
}
.nc-icon-mini.arrows-1_cloud-upload-96:before {
content: "\e90f";
}
.nc-icon-mini.arrows-1_curved-next:before {
content: "\e910";
}
.nc-icon-mini.arrows-1_curved-previous:before {
content: "\e911";
}
.nc-icon-mini.arrows-1_direction-53:before {
content: "\e912";
}
.nc-icon-mini.arrows-1_direction-56:before {
content: "\e913";
}
.nc-icon-mini.arrows-1_double-left:before {
content: "\e914";
}
.nc-icon-mini.arrows-1_double-right:before {
content: "\e915";
}
.nc-icon-mini.arrows-1_download:before {
content: "\e916";
}
.nc-icon-mini.arrows-1_enlarge-diagonal-43:before {
content: "\e917";
}
.nc-icon-mini.arrows-1_enlarge-diagonal-44:before {
content: "\e918";
}
.nc-icon-mini.arrows-1_enlarge-horizontal:before {
content: "\e919";
}
.nc-icon-mini.arrows-1_enlarge-vertical:before {
content: "\e91a";
}
.nc-icon-mini.arrows-1_fullscreen-70:before {
content: "\e91b";
}
.nc-icon-mini.arrows-1_fullscreen-71:before {
content: "\e91c";
}
.nc-icon-mini.arrows-1_fullscreen-76:before {
content: "\e91d";
}
.nc-icon-mini.arrows-1_fullscreen-77:before {
content: "\e91e";
}
.nc-icon-mini.arrows-1_fullscreen-double-74:before {
content: "\e91f";
}
.nc-icon-mini.arrows-1_fullscreen-double-75:before {
content: "\e920";
}
.nc-icon-mini.arrows-1_fullscreen-split-72:before {
content: "\e921";
}
.nc-icon-mini.arrows-1_fullscreen-split-73:before {
content: "\e922";
}
.nc-icon-mini.arrows-1_log-in:before {
content: "\e923";
}
.nc-icon-mini.arrows-1_log-out:before {
content: "\e924";
}
.nc-icon-mini.arrows-1_loop-82:before {
content: "\e925";
}
.nc-icon-mini.arrows-1_minimal-down:before {
content: "\e926";
}
.nc-icon-mini.arrows-1_minimal-left:before {
content: "\e927";
}
.nc-icon-mini.arrows-1_minimal-right:before {
content: "\e928";
}
.nc-icon-mini.arrows-1_minimal-up:before {
content: "\e929";
}
.nc-icon-mini.arrows-1_redo-79:before {
content: "\e92a";
}
.nc-icon-mini.arrows-1_redo-81:before {
content: "\e92b";
}
.nc-icon-mini.arrows-1_refresh-68:before {
content: "\e92c";
}
.nc-icon-mini.arrows-1_refresh-69:before {
content: "\e92d";
}
.nc-icon-mini.arrows-1_round-down:before {
content: "\e92e";
}
.nc-icon-mini.arrows-1_round-left:before {
content: "\e92f";
}
.nc-icon-mini.arrows-1_round-right:before {
content: "\e930";
}
.nc-icon-mini.arrows-1_round-up:before {
content: "\e931";
}
.nc-icon-mini.arrows-1_share-66:before {
content: "\e932";
}
.nc-icon-mini.arrows-1_share-91:before {
content: "\e933";
}
.nc-icon-mini.arrows-1_share-92:before {
content: "\e934";
}
.nc-icon-mini.arrows-1_shuffle-97:before {
content: "\e935";
}
.nc-icon-mini.arrows-1_shuffle-98:before {
content: "\e936";
}
.nc-icon-mini.arrows-1_simple-down:before {
content: "\e937";
}
.nc-icon-mini.arrows-1_simple-left:before {
content: "\e938";
}
.nc-icon-mini.arrows-1_simple-right:before {
content: "\e939";
}
.nc-icon-mini.arrows-1_simple-up:before {
content: "\e93a";
}
.nc-icon-mini.arrows-1_small-triangle-down:before {
content: "\e93b";
}
.nc-icon-mini.arrows-1_small-triangle-left:before {
content: "\e93c";
}
.nc-icon-mini.arrows-1_small-triangle-right:before {
content: "\e93d";
}
.nc-icon-mini.arrows-1_small-triangle-up:before {
content: "\e93e";
}
.nc-icon-mini.arrows-1_square-down:before {
content: "\e93f";
}
.nc-icon-mini.arrows-1_square-left:before {
content: "\e940";
}
.nc-icon-mini.arrows-1_square-right:before {
content: "\e941";
}
.nc-icon-mini.arrows-1_square-up:before {
content: "\e942";
}
.nc-icon-mini.arrows-1_strong-down:before {
content: "\e943";
}
.nc-icon-mini.arrows-1_strong-left:before {
content: "\e944";
}
.nc-icon-mini.arrows-1_strong-right:before {
content: "\e945";
}
.nc-icon-mini.arrows-1_strong-up:before {
content: "\e946";
}
.nc-icon-mini.arrows-1_tail-down:before {
content: "\e947";
}
.nc-icon-mini.arrows-1_tail-left:before {
content: "\e948";
}
.nc-icon-mini.arrows-1_tail-right:before {
content: "\e949";
}
.nc-icon-mini.arrows-1_tail-triangle-down:before {
content: "\e94a";
}
.nc-icon-mini.arrows-1_tail-triangle-left:before {
content: "\e94b";
}
.nc-icon-mini.arrows-1_tail-triangle-right:before {
content: "\e94c";
}
.nc-icon-mini.arrows-1_tail-triangle-up:before {
content: "\e94d";
}
.nc-icon-mini.arrows-1_tail-up:before {
content: "\e94e";
}
.nc-icon-mini.arrows-1_trend-down:before {
content: "\e94f";
}
.nc-icon-mini.arrows-1_trend-up:before {
content: "\e950";
}
.nc-icon-mini.arrows-1_triangle-down-20:before {
content: "\e951";
}
.nc-icon-mini.arrows-1_triangle-down-65:before {
content: "\e952";
}
.nc-icon-mini.arrows-1_triangle-left-18:before {
content: "\e953";
}
.nc-icon-mini.arrows-1_triangle-left-63:before {
content: "\e954";
}
.nc-icon-mini.arrows-1_triangle-right-17:before {
content: "\e955";
}
.nc-icon-mini.arrows-1_triangle-right-62:before {
content: "\e956";
}
.nc-icon-mini.arrows-1_triangle-up-19:before {
content: "\e957";
}
.nc-icon-mini.arrows-1_triangle-up-64:before {
content: "\e958";
}
.nc-icon-mini.arrows-1_zoom-88:before {
content: "\e959";
}
.nc-icon-mini.arrows-2_block-down:before {
content: "\e95a";
}
.nc-icon-mini.arrows-2_block-left:before {
content: "\e95b";
}
.nc-icon-mini.arrows-2_block-right:before {
content: "\e95c";
}
.nc-icon-mini.arrows-2_block-up:before {
content: "\e95d";
}
.nc-icon-mini.arrows-2_conversion:before {
content: "\e95e";
}
.nc-icon-mini.arrows-2_corner-down-round:before {
content: "\e95f";
}
.nc-icon-mini.arrows-2_corner-down:before {
content: "\e960";
}
.nc-icon-mini.arrows-2_corner-left-down:before {
content: "\e961";
}
.nc-icon-mini.arrows-2_corner-left-round:before {
content: "\e962";
}
.nc-icon-mini.arrows-2_corner-left:before {
content: "\e963";
}
.nc-icon-mini.arrows-2_corner-right-down:before {
content: "\e964";
}
.nc-icon-mini.arrows-2_corner-right-round:before {
content: "\e965";
}
.nc-icon-mini.arrows-2_corner-right:before {
content: "\e966";
}
.nc-icon-mini.arrows-2_corner-up-left:before {
content: "\e967";
}
.nc-icon-mini.arrows-2_corner-up-right:before {
content: "\e968";
}
.nc-icon-mini.arrows-2_corner-up-round:before {
content: "\e969";
}
.nc-icon-mini.arrows-2_corner-up:before {
content: "\e96a";
}
.nc-icon-mini.arrows-2_cross-down:before {
content: "\e96b";
}
.nc-icon-mini.arrows-2_cross-left:before {
content: "\e96c";
}
.nc-icon-mini.arrows-2_cross-right:before {
content: "\e96d";
}
.nc-icon-mini.arrows-2_cross-up:before {
content: "\e96e";
}
.nc-icon-mini.arrows-2_delete-49:before {
content: "\e96f";
}
.nc-icon-mini.arrows-2_delete-50:before {
content: "\e970";
}
.nc-icon-mini.arrows-2_direction:before {
content: "\e971";
}
.nc-icon-mini.arrows-2_dots-download:before {
content: "\e972";
}
.nc-icon-mini.arrows-2_dots-upload:before {
content: "\e973";
}
.nc-icon-mini.arrows-2_eject:before {
content: "\e974";
}
.nc-icon-mini.arrows-2_enlarge-circle:before {
content: "\e975";
}
.nc-icon-mini.arrows-2_file-download-87:before {
content: "\e976";
}
.nc-icon-mini.arrows-2_file-upload-86:before {
content: "\e977";
}
.nc-icon-mini.arrows-2_hit-down:before {
content: "\e978";
}
.nc-icon-mini.arrows-2_hit-left:before {
content: "\e979";
}
.nc-icon-mini.arrows-2_hit-right:before {
content: "\e97a";
}
.nc-icon-mini.arrows-2_hit-up:before {
content: "\e97b";
}
.nc-icon-mini.arrows-2_log-out:before {
content: "\e97c";
}
.nc-icon-mini.arrows-2_move-down-right:before {
content: "\e97d";
}
.nc-icon-mini.arrows-2_move-down:before {
content: "\e97e";
}
.nc-icon-mini.arrows-2_move-left:before {
content: "\e97f";
}
.nc-icon-mini.arrows-2_move-right:before {
content: "\e980";
}
.nc-icon-mini.arrows-2_move-up-left:before {
content: "\e981";
}
.nc-icon-mini.arrows-2_move-up:before {
content: "\e982";
}
.nc-icon-mini.arrows-2_push-next:before {
content: "\e983";
}
.nc-icon-mini.arrows-2_push-previous:before {
content: "\e984";
}
.nc-icon-mini.arrows-2_replay:before {
content: "\e985";
}
.nc-icon-mini.arrows-2_round-left-down:before {
content: "\e986";
}
.nc-icon-mini.arrows-2_round-right-down:before {
content: "\e987";
}
.nc-icon-mini.arrows-2_round-up-left:before {
content: "\e988";
}
.nc-icon-mini.arrows-2_round-up-right:before {
content: "\e989";
}
.nc-icon-mini.arrows-2_select-83:before {
content: "\e98a";
}
.nc-icon-mini.arrows-2_select-84:before {
content: "\e98b";
}
.nc-icon-mini.arrows-2_skew-down:before {
content: "\e98c";
}
.nc-icon-mini.arrows-2_skew-left:before {
content: "\e98d";
}
.nc-icon-mini.arrows-2_skew-right:before {
content: "\e98e";
}
.nc-icon-mini.arrows-2_skew-up:before {
content: "\e98f";
}
.nc-icon-mini.arrows-2_small-left:before {
content: "\e990";
}
.nc-icon-mini.arrows-2_small-right:before {
content: "\e991";
}
.nc-icon-mini.arrows-2_square-download:before {
content: "\e992";
}
.nc-icon-mini.arrows-2_square-upload:before {
content: "\e993";
}
.nc-icon-mini.arrows-2_triangle-down:before {
content: "\e994";
}
.nc-icon-mini.arrows-2_triangle-left:before {
content: "\e995";
}
.nc-icon-mini.arrows-2_triangle-right:before {
content: "\e996";
}
.nc-icon-mini.arrows-2_triangle-up:before {
content: "\e997";
}
.nc-icon-mini.arrows-2_zoom:before {
content: "\e998";
}
.nc-icon-mini.arrows-3_circle-simple-down:before {
content: "\e999";
}
.nc-icon-mini.arrows-3_circle-simple-left:before {
content: "\e99a";
}
.nc-icon-mini.arrows-3_circle-simple-right:before {
content: "\e99b";
}
.nc-icon-mini.arrows-3_circle-simple-up:before {
content: "\e99c";
}
.nc-icon-mini.arrows-3_small-down:before {
content: "\e99d";
}
.nc-icon-mini.arrows-3_small-up:before {
content: "\e99e";
}
.nc-icon-mini.arrows-3_square-corner-down-left:before {
content: "\e99f";
}
.nc-icon-mini.arrows-3_square-corner-down-right:before {
content: "\e9a0";
}
.nc-icon-mini.arrows-3_square-corner-up-left:before {
content: "\e9a1";
}
.nc-icon-mini.arrows-3_square-corner-up-right:before {
content: "\e9a2";
}
.nc-icon-mini.arrows-3_square-down-06:before {
content: "\e9a3";
}
.nc-icon-mini.arrows-3_square-left-04:before {
content: "\e9a4";
}
.nc-icon-mini.arrows-3_square-right-03:before {
content: "\e9a5";
}
.nc-icon-mini.arrows-3_square-simple-down:before {
content: "\e9a6";
}
.nc-icon-mini.arrows-3_square-simple-left:before {
content: "\e9a7";
}
.nc-icon-mini.arrows-3_square-simple-right:before {
content: "\e9a8";
}
.nc-icon-mini.arrows-3_square-simple-up:before {
content: "\e9a9";
}
.nc-icon-mini.arrows-3_square-up-05:before {
content: "\e9aa";
}
.nc-icon-mini.arrows-e_archive-e-download:before {
content: "\e9ab";
}
.nc-icon-mini.arrows-e_archive-e-upload:before {
content: "\e9ac";
}
.nc-icon-mini.arrows-e_circle-e-down-04:before {
content: "\e9ad";
}
.nc-icon-mini.arrows-e_circle-e-down-12:before {
content: "\e9ae";
}
.nc-icon-mini.arrows-e_circle-e-left-02:before {
content: "\e9af";
}
.nc-icon-mini.arrows-e_circle-e-left-10:before {
content: "\e9b0";
}
.nc-icon-mini.arrows-e_circle-e-right-01:before {
content: "\e9b1";
}
.nc-icon-mini.arrows-e_circle-e-right-09:before {
content: "\e9b2";
}
.nc-icon-mini.arrows-e_circle-e-up-03:before {
content: "\e9b3";
}
.nc-icon-mini.arrows-e_circle-e-up-11:before {
content: "\e9b4";
}
.nc-icon-mini.arrows-e_enlarge-21:before {
content: "\e9b5";
}
.nc-icon-mini.arrows-e_enlarge-22:before {
content: "\e9b6";
}
.nc-icon-mini.arrows-e_expand:before {
content: "\e9b7";
}
.nc-icon-mini.arrows-e_move-bottom-right:before {
content: "\e9b8";
}
.nc-icon-mini.arrows-e_move-top-left:before {
content: "\e9b9";
}
.nc-icon-mini.arrows-e_refresh-19:before {
content: "\e9ba";
}
.nc-icon-mini.arrows-e_refresh-20:before {
content: "\e9bb";
}
.nc-icon-mini.arrows-e_resize-h:before {
content: "\e9bc";
}
.nc-icon-mini.arrows-e_resize-v:before {
content: "\e9bd";
}
.nc-icon-mini.arrows-e_share-26:before {
content: "\e9be";
}
.nc-icon-mini.arrows-e_share-27:before {
content: "\e9bf";
}
.nc-icon-mini.arrows-e_square-e-down:before {
content: "\e9c0";
}
.nc-icon-mini.arrows-e_square-e-left:before {
content: "\e9c1";
}
.nc-icon-mini.arrows-e_square-e-right:before {
content: "\e9c2";
}
.nc-icon-mini.arrows-e_square-e-up:before {
content: "\e9c3";
}
.nc-icon-mini.arrows-e_zoom-circle:before {
content: "\e9c4";
}
.nc-icon-mini.arrows-e_zoom-e:before {
content: "\e9c5";
}
.nc-icon-mini.arrows-e_zoom-square:before {
content: "\e9c6";
}
.nc-icon-mini.arrows-e_zoom-triangles:before {
content: "\e9c7";
}
.nc-icon-mini.weather_celsius:before {
content: "\e9c8";
}
.nc-icon-mini.weather_cloud-drop:before {
content: "\e9c9";
}
.nc-icon-mini.weather_cloud-fog-31:before {
content: "\e9ca";
}
.nc-icon-mini.weather_cloud-fog-32:before {
content: "\e9cb";
}
.nc-icon-mini.weather_cloud-hail:before {
content: "\e9cc";
}
.nc-icon-mini.weather_cloud-light:before {
content: "\e9cd";
}
.nc-icon-mini.weather_cloud-moon:before {
content: "\e9ce";
}
.nc-icon-mini.weather_cloud-rain:before {
content: "\e9cf";
}
.nc-icon-mini.weather_cloud-snow-34:before {
content: "\e9d0";
}
.nc-icon-mini.weather_cloud-snow-42:before {
content: "\e9d1";
}
.nc-icon-mini.weather_cloud-sun-17:before {
content: "\e9d2";
}
.nc-icon-mini.weather_compass:before {
content: "\e9d3";
}
.nc-icon-mini.weather_drop-15:before {
content: "\e9d4";
}
.nc-icon-mini.weather_drops:before {
content: "\e9d5";
}
.nc-icon-mini.weather_eclipse:before {
content: "\e9d6";
}
.nc-icon-mini.weather_fahrenheit:before {
content: "\e9d7";
}
.nc-icon-mini.weather_fog:before {
content: "\e9d8";
}
.nc-icon-mini.weather_forecast:before {
content: "\e9d9";
}
.nc-icon-mini.weather_hurricane-44:before {
content: "\e9da";
}
.nc-icon-mini.weather_hurricane-45:before {
content: "\e9db";
}
.nc-icon-mini.weather_moon-cloud-drop:before {
content: "\e9dc";
}
.nc-icon-mini.weather_moon-cloud-fog:before {
content: "\e9dd";
}
.nc-icon-mini.weather_moon-cloud-hail:before {
content: "\e9de";
}
.nc-icon-mini.weather_moon-cloud-light:before {
content: "\e9df";
}
.nc-icon-mini.weather_moon-cloud-rain:before {
content: "\e9e0";
}
.nc-icon-mini.weather_moon-cloud-snow-61:before {
content: "\e9e1";
}
.nc-icon-mini.weather_moon-cloud-snow-62:before {
content: "\e9e2";
}
.nc-icon-mini.weather_moon-fog:before {
content: "\e9e3";
}
.nc-icon-mini.weather_moon-full:before {
content: "\e9e4";
}
.nc-icon-mini.weather_moon-stars:before {
content: "\e9e5";
}
.nc-icon-mini.weather_moon:before {
content: "\e9e6";
}
.nc-icon-mini.weather_rain-hail:before {
content: "\e9e7";
}
.nc-icon-mini.weather_rain:before {
content: "\e9e8";
}
.nc-icon-mini.weather_rainbow:before {
content: "\e9e9";
}
.nc-icon-mini.weather_snow:before {
content: "\e9ea";
}
.nc-icon-mini.weather_sun-cloud-drop:before {
content: "\e9eb";
}
.nc-icon-mini.weather_sun-cloud-fog:before {
content: "\e9ec";
}
.nc-icon-mini.weather_sun-cloud-hail:before {
content: "\e9ed";
}
.nc-icon-mini.weather_sun-cloud-light:before {
content: "\e9ee";
}
.nc-icon-mini.weather_sun-cloud-rain:before {
content: "\e9ef";
}
.nc-icon-mini.weather_sun-cloud-snow-54:before {
content: "\e9f0";
}
.nc-icon-mini.weather_sun-cloud-snow-55:before {
content: "\e9f1";
}
.nc-icon-mini.weather_sun-cloud:before {
content: "\e9f2";
}
.nc-icon-mini.weather_sun-fog-30:before {
content: "\e9f3";
}
.nc-icon-mini.weather_sun-fog-43:before {
content: "\e9f4";
}
.nc-icon-mini.weather_wind:before {
content: "\e9f5";
}
.nc-icon-mini.weather-e_cloud-01:before {
content: "\e9f6";
}
.nc-icon-mini.weather-e_cloud-02:before {
content: "\e9f7";
}
.nc-icon-mini.users_add-27:before {
content: "\e9f8";
}
.nc-icon-mini.users_add-29:before {
content: "\e9f9";
}
.nc-icon-mini.users_badge-13:before {
content: "\e9fa";
}
.nc-icon-mini.users_badge-14:before {
content: "\e9fb";
}
.nc-icon-mini.users_badge-15:before {
content: "\e9fc";
}
.nc-icon-mini.users_circle-08:before {
content: "\e9fd";
}
.nc-icon-mini.users_circle-09:before {
content: "\e9fe";
}
.nc-icon-mini.users_circle-10:before {
content: "\e9ff";
}
.nc-icon-mini.users_delete-28:before {
content: "\ea00";
}
.nc-icon-mini.users_delete-30:before {
content: "\ea01";
}
.nc-icon-mini.users_man-20:before {
content: "\ea02";
}
.nc-icon-mini.users_multiple-11:before {
content: "\ea03";
}
.nc-icon-mini.users_multiple-19:before {
content: "\ea04";
}
.nc-icon-mini.users_single-01:before {
content: "\ea05";
}
.nc-icon-mini.users_single-02:before {
content: "\ea06";
}
.nc-icon-mini.users_single-03:before {
content: "\ea07";
}
.nc-icon-mini.users_single-04:before {
content: "\ea08";
}
.nc-icon-mini.users_single-05:before {
content: "\ea09";
}
.nc-icon-mini.users_single-body:before {
content: "\ea0a";
}
.nc-icon-mini.users_square-31:before {
content: "\ea0b";
}
.nc-icon-mini.users_square-32:before {
content: "\ea0c";
}
.nc-icon-mini.users_square-33:before {
content: "\ea0d";
}
.nc-icon-mini.users_woman-21:before {
content: "\ea0e";
}
.nc-icon-mini.ui-1_analytics-88:before {
content: "\ea0f";
}
.nc-icon-mini.ui-1_analytics-89:before {
content: "\ea10";
}
.nc-icon-mini.ui-1_attach-86:before {
content: "\ea11";
}
.nc-icon-mini.ui-1_attach-87:before {
content: "\ea12";
}
.nc-icon-mini.ui-1_bell-53:before {
content: "\ea13";
}
.nc-icon-mini.ui-1_bell-54:before {
content: "\ea14";
}
.nc-icon-mini.ui-1_bell-55:before {
content: "\ea15";
}
.nc-icon-mini.ui-1_bold-add:before {
content: "\ea16";
}
.nc-icon-mini.ui-1_bold-delete:before {
content: "\ea17";
}
.nc-icon-mini.ui-1_bold-remove:before {
content: "\ea18";
}
.nc-icon-mini.ui-1_bookmark-add:before {
content: "\ea19";
}
.nc-icon-mini.ui-1_bookmark-remove:before {
content: "\ea1a";
}
.nc-icon-mini.ui-1_calendar-57:before {
content: "\ea1b";
}
.nc-icon-mini.ui-1_calendar-60:before {
content: "\ea1c";
}
.nc-icon-mini.ui-1_check-bold:before {
content: "\ea1d";
}
.nc-icon-mini.ui-1_check-circle-07:before {
content: "\ea1e";
}
.nc-icon-mini.ui-1_check-circle-08:before {
content: "\ea1f";
}
.nc-icon-mini.ui-1_check-curve:before {
content: "\ea20";
}
.nc-icon-mini.ui-1_check-simple:before {
content: "\ea21";
}
.nc-icon-mini.ui-1_check-small:before {
content: "\ea22";
}
.nc-icon-mini.ui-1_check-square-09:before {
content: "\ea23";
}
.nc-icon-mini.ui-1_check-square-11:before {
content: "\ea24";
}
.nc-icon-mini.ui-1_check:before {
content: "\ea25";
}
.nc-icon-mini.ui-1_circle-add:before {
content: "\ea26";
}
.nc-icon-mini.ui-1_circle-bold-add:before {
content: "\ea27";
}
.nc-icon-mini.ui-1_circle-bold-remove:before {
content: "\ea28";
}
.nc-icon-mini.ui-1_circle-delete:before {
content: "\ea29";
}
.nc-icon-mini.ui-1_circle-remove:before {
content: "\ea2a";
}
.nc-icon-mini.ui-1_dashboard-29:before {
content: "\ea2b";
}
.nc-icon-mini.ui-1_dashboard-30:before {
content: "\ea2c";
}
.nc-icon-mini.ui-1_dashboard-half:before {
content: "\ea2d";
}
.nc-icon-mini.ui-1_dashboard-level:before {
content: "\ea2e";
}
.nc-icon-mini.ui-1_database:before {
content: "\ea2f";
}
.nc-icon-mini.ui-1_drop:before {
content: "\ea30";
}
.nc-icon-mini.ui-1_edit-71:before {
content: "\ea31";
}
.nc-icon-mini.ui-1_edit-72:before {
content: "\ea32";
}
.nc-icon-mini.ui-1_edit-73:before {
content: "\ea33";
}
.nc-icon-mini.ui-1_edit-74:before {
content: "\ea34";
}
.nc-icon-mini.ui-1_edit-75:before {
content: "\ea35";
}
.nc-icon-mini.ui-1_edit-76:before {
content: "\ea36";
}
.nc-icon-mini.ui-1_edit-77:before {
content: "\ea37";
}
.nc-icon-mini.ui-1_edit-78:before {
content: "\ea38";
}
.nc-icon-mini.ui-1_email-83:before {
content: "\ea39";
}
.nc-icon-mini.ui-1_email-84:before {
content: "\ea3a";
}
.nc-icon-mini.ui-1_eye-17:before {
content: "\ea3b";
}
.nc-icon-mini.ui-1_eye-19:before {
content: "\ea3c";
}
.nc-icon-mini.ui-1_eye-ban-18:before {
content: "\ea3d";
}
.nc-icon-mini.ui-1_eye-ban-20:before {
content: "\ea3e";
}
.nc-icon-mini.ui-1_flame:before {
content: "\ea3f";
}
.nc-icon-mini.ui-1_home-51:before {
content: "\ea40";
}
.nc-icon-mini.ui-1_home-52:before {
content: "\ea41";
}
.nc-icon-mini.ui-1_home-minimal:before {
content: "\ea42";
}
.nc-icon-mini.ui-1_home-simple:before {
content: "\ea43";
}
.nc-icon-mini.ui-1_leaf-80:before {
content: "\ea44";
}
.nc-icon-mini.ui-1_leaf-81:before {
content: "\ea45";
}
.nc-icon-mini.ui-1_leaf-edit:before {
content: "\ea46";
}
.nc-icon-mini.ui-1_lock-circle-open:before {
content: "\ea47";
}
.nc-icon-mini.ui-1_lock-circle:before {
content: "\ea48";
}
.nc-icon-mini.ui-1_lock-open:before {
content: "\ea49";
}
.nc-icon-mini.ui-1_lock:before {
content: "\ea4a";
}
.nc-icon-mini.ui-1_notification-69:before {
content: "\ea4b";
}
.nc-icon-mini.ui-1_pencil:before {
content: "\ea4c";
}
.nc-icon-mini.ui-1_preferences-circle-rotate:before {
content: "\ea4d";
}
.nc-icon-mini.ui-1_preferences-circle:before {
content: "\ea4e";
}
.nc-icon-mini.ui-1_preferences-container-circle-rotate:before {
content: "\ea4f";
}
.nc-icon-mini.ui-1_preferences-container-circle:before {
content: "\ea50";
}
.nc-icon-mini.ui-1_preferences-container-rotate:before {
content: "\ea51";
}
.nc-icon-mini.ui-1_preferences-container:before {
content: "\ea52";
}
.nc-icon-mini.ui-1_preferences-rotate:before {
content: "\ea53";
}
.nc-icon-mini.ui-1_preferences:before {
content: "\ea54";
}
.nc-icon-mini.ui-1_send:before {
content: "\ea55";
}
.nc-icon-mini.ui-1_settings-gear-63:before {
content: "\ea56";
}
.nc-icon-mini.ui-1_settings-gear-64:before {
content: "\ea57";
}
.nc-icon-mini.ui-1_settings-gear-65:before {
content: "\ea58";
}
.nc-icon-mini.ui-1_settings-tool-66:before {
content: "\ea59";
}
.nc-icon-mini.ui-1_simple-add:before {
content: "\ea5a";
}
.nc-icon-mini.ui-1_simple-delete:before {
content: "\ea5b";
}
.nc-icon-mini.ui-1_simple-remove:before {
content: "\ea5c";
}
.nc-icon-mini.ui-1_trash-simple:before {
content: "\ea5d";
}
.nc-icon-mini.ui-1_trash:before {
content: "\ea5e";
}
.nc-icon-mini.ui-1_ui-03:before {
content: "\ea5f";
}
.nc-icon-mini.ui-1_ui-04:before {
content: "\ea60";
}
.nc-icon-mini.ui-1_zoom-bold-in:before {
content: "\ea61";
}
.nc-icon-mini.ui-1_zoom-bold-out:before {
content: "\ea62";
}
.nc-icon-mini.ui-1_zoom-bold:before {
content: "\ea63";
}
.nc-icon-mini.ui-1_zoom-in:before {
content: "\ea64";
}
.nc-icon-mini.ui-1_zoom-out:before {
content: "\ea65";
}
.nc-icon-mini.ui-1_zoom:before {
content: "\ea66";
}
.nc-icon-mini.ui-2_alert:before {
content: "\ea67";
}
.nc-icon-mini.ui-2_alert-:before {
content: "\ea68";
}
.nc-icon-mini.ui-2_alert-circle:before {
content: "\ea69";
}
.nc-icon-mini.ui-2_alert-circle-:before {
content: "\ea6a";
}
.nc-icon-mini.ui-2_alert-circle-i:before {
content: "\ea6b";
}
.nc-icon-mini.ui-2_alert-i:before {
content: "\ea6c";
}
.nc-icon-mini.ui-2_alert-square:before {
content: "\ea6d";
}
.nc-icon-mini.ui-2_alert-square-:before {
content: "\ea6e";
}
.nc-icon-mini.ui-2_alert-square-i:before {
content: "\ea6f";
}
.nc-icon-mini.ui-2_archive:before {
content: "\ea70";
}
.nc-icon-mini.ui-2_ban:before {
content: "\ea71";
}
.nc-icon-mini.ui-2_battery-81:before {
content: "\ea72";
}
.nc-icon-mini.ui-2_battery-83:before {
content: "\ea73";
}
.nc-icon-mini.ui-2_battery-half:before {
content: "\ea74";
}
.nc-icon-mini.ui-2_battery-low:before {
content: "\ea75";
}
.nc-icon-mini.ui-2_bluetooth:before {
content: "\ea76";
}
.nc-icon-mini.ui-2_book:before {
content: "\ea77";
}
.nc-icon-mini.ui-2_chart-bar-52:before {
content: "\ea78";
}
.nc-icon-mini.ui-2_chart-bar-53:before {
content: "\ea79";
}
.nc-icon-mini.ui-2_chat-content:before {
content: "\ea7a";
}
.nc-icon-mini.ui-2_chat-round-content:before {
content: "\ea7b";
}
.nc-icon-mini.ui-2_chat-round:before {
content: "\ea7c";
}
.nc-icon-mini.ui-2_chat:before {
content: "\ea7d";
}
.nc-icon-mini.ui-2_circle-bold-delete:before {
content: "\ea7e";
}
.nc-icon-mini.ui-2_cloud-25:before {
content: "\ea7f";
}
.nc-icon-mini.ui-2_cloud-26:before {
content: "\ea80";
}
.nc-icon-mini.ui-2_disk:before {
content: "\ea81";
}
.nc-icon-mini.ui-2_enlarge-57:before {
content: "\ea82";
}
.nc-icon-mini.ui-2_enlarge-58:before {
content: "\ea83";
}
.nc-icon-mini.ui-2_favourite-28:before {
content: "\ea84";
}
.nc-icon-mini.ui-2_favourite-31:before {
content: "\ea85";
}
.nc-icon-mini.ui-2_filter:before {
content: "\ea86";
}
.nc-icon-mini.ui-2_fullsize:before {
content: "\ea87";
}
.nc-icon-mini.ui-2_grid-45:before {
content: "\ea88";
}
.nc-icon-mini.ui-2_grid-46:before {
content: "\ea89";
}
.nc-icon-mini.ui-2_grid-48:before {
content: "\ea8a";
}
.nc-icon-mini.ui-2_grid-50:before {
content: "\ea8b";
}
.nc-icon-mini.ui-2_grid-square:before {
content: "\ea8c";
}
.nc-icon-mini.ui-2_hourglass:before {
content: "\ea8d";
}
.nc-icon-mini.ui-2_lab:before {
content: "\ea8e";
}
.nc-icon-mini.ui-2_layers:before {
content: "\ea8f";
}
.nc-icon-mini.ui-2_like:before {
content: "\ea90";
}
.nc-icon-mini.ui-2_link-66:before {
content: "\ea91";
}
.nc-icon-mini.ui-2_link-68:before {
content: "\ea92";
}
.nc-icon-mini.ui-2_link-69:before {
content: "\ea93";
}
.nc-icon-mini.ui-2_link-72:before {
content: "\ea94";
}
.nc-icon-mini.ui-2_link-broken-70:before {
content: "\ea95";
}
.nc-icon-mini.ui-2_link-broken-73:before {
content: "\ea96";
}
.nc-icon-mini.ui-2_menu-34:before {
content: "\ea97";
}
.nc-icon-mini.ui-2_menu-35:before {
content: "\ea98";
}
.nc-icon-mini.ui-2_menu-bold:before {
content: "\ea99";
}
.nc-icon-mini.ui-2_menu-dots:before {
content: "\ea9a";
}
.nc-icon-mini.ui-2_menu-square:before {
content: "\ea9b";
}
.nc-icon-mini.ui-2_node:before {
content: "\ea9c";
}
.nc-icon-mini.ui-2_paragraph:before {
content: "\ea9d";
}
.nc-icon-mini.ui-2_phone:before {
content: "\ea9e";
}
.nc-icon-mini.ui-2_share-bold:before {
content: "\ea9f";
}
.nc-icon-mini.ui-2_share:before {
content: "\eaa0";
}
.nc-icon-mini.ui-2_small-add:before {
content: "\eaa1";
}
.nc-icon-mini.ui-2_small-delete:before {
content: "\eaa2";
}
.nc-icon-mini.ui-2_small-remove:before {
content: "\eaa3";
}
.nc-icon-mini.ui-2_square-add-08:before {
content: "\eaa4";
}
.nc-icon-mini.ui-2_square-add-11:before {
content: "\eaa5";
}
.nc-icon-mini.ui-2_square-delete-10:before {
content: "\eaa6";
}
.nc-icon-mini.ui-2_square-delete-13:before {
content: "\eaa7";
}
.nc-icon-mini.ui-2_square-remove-09:before {
content: "\eaa8";
}
.nc-icon-mini.ui-2_square-remove-12:before {
content: "\eaa9";
}
.nc-icon-mini.ui-2_target:before {
content: "\eaaa";
}
.nc-icon-mini.ui-2_tile-55:before {
content: "\eaab";
}
.nc-icon-mini.ui-2_tile-56:before {
content: "\eaac";
}
.nc-icon-mini.ui-2_time-alarm:before {
content: "\eaad";
}
.nc-icon-mini.ui-2_time-clock:before {
content: "\eaae";
}
.nc-icon-mini.ui-2_time-countdown:before {
content: "\eaaf";
}
.nc-icon-mini.ui-2_time:before {
content: "\eab0";
}
.nc-icon-mini.ui-2_webpage:before {
content: "\eab1";
}
.nc-icon-mini.ui-e_round-e-alert:before {
content: "\eab2";
}
.nc-icon-mini.ui-e_round-e-help:before {
content: "\eab3";
}
.nc-icon-mini.ui-e_round-e-info:before {
content: "\eab4";
}
.nc-icon-mini.ui-e_square-e-alert:before {
content: "\eab5";
}
.nc-icon-mini.ui-e_square-e-help:before {
content: "\eab6";
}
.nc-icon-mini.ui-e_square-e-info:before {
content: "\eab7";
}
.nc-icon-mini.ui-e_star-half:before {
content: "\eab8";
}
.nc-icon-mini.travel_backpack:before {
content: "\eab9";
}
.nc-icon-mini.travel_bag:before {
content: "\eaba";
}
.nc-icon-mini.travel_camping:before {
content: "\eabb";
}
.nc-icon-mini.travel_drink:before {
content: "\eabc";
}
.nc-icon-mini.travel_explore:before {
content: "\eabd";
}
.nc-icon-mini.travel_fire:before {
content: "\eabe";
}
.nc-icon-mini.travel_hotel-bell:before {
content: "\eabf";
}
.nc-icon-mini.travel_hotel-symbol:before {
content: "\eac0";
}
.nc-icon-mini.travel_hotel:before {
content: "\eac1";
}
.nc-icon-mini.travel_igloo:before {
content: "\eac2";
}
.nc-icon-mini.travel_jellyfish:before {
content: "\eac3";
}
.nc-icon-mini.travel_lamp:before {
content: "\eac4";
}
.nc-icon-mini.travel_luggage:before {
content: "\eac5";
}
.nc-icon-mini.travel_octopus:before {
content: "\eac6";
}
.nc-icon-mini.travel_passport:before {
content: "\eac7";
}
.nc-icon-mini.travel_pickaxe:before {
content: "\eac8";
}
.nc-icon-mini.travel_pool:before {
content: "\eac9";
}
.nc-icon-mini.travel_rackets:before {
content: "\eaca";
}
.nc-icon-mini.travel_road-sign-left:before {
content: "\eacb";
}
.nc-icon-mini.travel_road-sign-right:before {
content: "\eacc";
}
.nc-icon-mini.travel_rowing:before {
content: "\eacd";
}
.nc-icon-mini.travel_sea-mask:before {
content: "\eace";
}
.nc-icon-mini.travel_shark:before {
content: "\eacf";
}
.nc-icon-mini.travel_spa:before {
content: "\ead0";
}
.nc-icon-mini.travel_sunglasses:before {
content: "\ead1";
}
.nc-icon-mini.travel_surf:before {
content: "\ead2";
}
.nc-icon-mini.travel_swimwear:before {
content: "\ead3";
}
.nc-icon-mini.travel_swiss-knife:before {
content: "\ead4";
}
.nc-icon-mini.travel_trolley:before {
content: "\ead5";
}
.nc-icon-mini.travel_world:before {
content: "\ead6";
}
.nc-icon-mini.transportation_air-baloon:before {
content: "\ead7";
}
.nc-icon-mini.transportation_bike:before {
content: "\ead8";
}
.nc-icon-mini.transportation_boat-small-02:before {
content: "\ead9";
}
.nc-icon-mini.transportation_boat-small-03:before {
content: "\eada";
}
.nc-icon-mini.transportation_boat:before {
content: "\eadb";
}
.nc-icon-mini.transportation_bus-front-10:before {
content: "\eadc";
}
.nc-icon-mini.transportation_bus-front-12:before {
content: "\eadd";
}
.nc-icon-mini.transportation_car-front:before {
content: "\eade";
}
.nc-icon-mini.transportation_car-taxi:before {
content: "\eadf";
}
.nc-icon-mini.transportation_car:before {
content: "\eae0";
}
.nc-icon-mini.transportation_helicopter:before {
content: "\eae1";
}
.nc-icon-mini.transportation_helmet:before {
content: "\eae2";
}
.nc-icon-mini.transportation_light-traffic:before {
content: "\eae3";
}
.nc-icon-mini.transportation_moto:before {
content: "\eae4";
}
.nc-icon-mini.transportation_plane-17:before {
content: "\eae5";
}
.nc-icon-mini.transportation_plane-18:before {
content: "\eae6";
}
.nc-icon-mini.transportation_road:before {
content: "\eae7";
}
.nc-icon-mini.transportation_skateboard:before {
content: "\eae8";
}
.nc-icon-mini.transportation_tractor:before {
content: "\eae9";
}
.nc-icon-mini.transportation_train:before {
content: "\eaea";
}
.nc-icon-mini.transportation_tram:before {
content: "\eaeb";
}
.nc-icon-mini.transportation_truck-front:before {
content: "\eaec";
}
.nc-icon-mini.transportation_vespa-front:before {
content: "\eaed";
}
.nc-icon-mini.gestures_2x-drag-down:before {
content: "\eaee";
}
.nc-icon-mini.gestures_2x-drag-up:before {
content: "\eaef";
}
.nc-icon-mini.gestures_active-38:before {
content: "\eaf0";
}
.nc-icon-mini.gestures_active-40:before {
content: "\eaf1";
}
.nc-icon-mini.gestures_camera:before {
content: "\eaf2";
}
.nc-icon-mini.gestures_double-tap:before {
content: "\eaf3";
}
.nc-icon-mini.gestures_drag-21:before {
content: "\eaf4";
}
.nc-icon-mini.gestures_drag-31:before {
content: "\eaf5";
}
.nc-icon-mini.gestures_drag-down:before {
content: "\eaf6";
}
.nc-icon-mini.gestures_drag-left:before {
content: "\eaf7";
}
.nc-icon-mini.gestures_drag-right:before {
content: "\eaf8";
}
.nc-icon-mini.gestures_drag-up:before {
content: "\eaf9";
}
.nc-icon-mini.gestures_grab:before {
content: "\eafa";
}
.nc-icon-mini.gestures_pin:before {
content: "\eafb";
}
.nc-icon-mini.gestures_pinch:before {
content: "\eafc";
}
.nc-icon-mini.gestures_rotate-22:before {
content: "\eafd";
}
.nc-icon-mini.gestures_scan:before {
content: "\eafe";
}
.nc-icon-mini.gestures_stretch:before {
content: "\eaff";
}
.nc-icon-mini.gestures_swipe-bottom:before {
content: "\eb00";
}
.nc-icon-mini.gestures_swipe-left:before {
content: "\eb01";
}
.nc-icon-mini.gestures_swipe-right:before {
content: "\eb02";
}
.nc-icon-mini.gestures_swipe-up:before {
content: "\eb03";
}
.nc-icon-mini.gestures_tap-01:before {
content: "\eb04";
}
.nc-icon-mini.gestures_tap-02:before {
content: "\eb05";
}
.nc-icon-mini.text_align-center:before {
content: "\eb06";
}
.nc-icon-mini.text_align-justify:before {
content: "\eb07";
}
.nc-icon-mini.text_align-left:before {
content: "\eb08";
}
.nc-icon-mini.text_align-right:before {
content: "\eb09";
}
.nc-icon-mini.text_background:before {
content: "\eb0a";
}
.nc-icon-mini.text_bold:before {
content: "\eb0b";
}
.nc-icon-mini.text_capitalize:before {
content: "\eb0c";
}
.nc-icon-mini.text_caps-small:before {
content: "\eb0d";
}
.nc-icon-mini.text_color:before {
content: "\eb0e";
}
.nc-icon-mini.text_edit:before {
content: "\eb0f";
}
.nc-icon-mini.text_italic:before {
content: "\eb10";
}
.nc-icon-mini.text_line-height:before {
content: "\eb11";
}
.nc-icon-mini.text_list-bullet:before {
content: "\eb12";
}
.nc-icon-mini.text_list-numbers:before {
content: "\eb13";
}
.nc-icon-mini.text_margin-left:before {
content: "\eb14";
}
.nc-icon-mini.text_margin-right:before {
content: "\eb15";
}
.nc-icon-mini.text_quote:before {
content: "\eb16";
}
.nc-icon-mini.text_scale-horizontal:before {
content: "\eb17";
}
.nc-icon-mini.text_scale-vertical:before {
content: "\eb18";
}
.nc-icon-mini.text_size:before {
content: "\eb19";
}
.nc-icon-mini.text_strikethrough:before {
content: "\eb1a";
}
.nc-icon-mini.text_subscript:before {
content: "\eb1b";
}
.nc-icon-mini.text_superscript:before {
content: "\eb1c";
}
.nc-icon-mini.text_tracking:before {
content: "\eb1d";
}
.nc-icon-mini.text_underline:before {
content: "\eb1e";
}
.nc-icon-mini.tech_cable-49:before {
content: "\eb1f";
}
.nc-icon-mini.tech_cd-reader:before {
content: "\eb20";
}
.nc-icon-mini.tech_computer-monitor:before {
content: "\eb21";
}
.nc-icon-mini.tech_computer-old:before {
content: "\eb22";
}
.nc-icon-mini.tech_computer:before {
content: "\eb23";
}
.nc-icon-mini.tech_controller-modern:before {
content: "\eb24";
}
.nc-icon-mini.tech_controller:before {
content: "\eb25";
}
.nc-icon-mini.tech_desktop-screen:before {
content: "\eb26";
}
.nc-icon-mini.tech_desktop:before {
content: "\eb27";
}
.nc-icon-mini.tech_disk-reader:before {
content: "\eb28";
}
.nc-icon-mini.tech_disk:before {
content: "\eb29";
}
.nc-icon-mini.tech_headphones:before {
content: "\eb2a";
}
.nc-icon-mini.tech_keyboard-wifi:before {
content: "\eb2b";
}
.nc-icon-mini.tech_keyboard:before {
content: "\eb2c";
}
.nc-icon-mini.tech_laptop-camera:before {
content: "\eb2d";
}
.nc-icon-mini.tech_laptop-front:before {
content: "\eb2e";
}
.nc-icon-mini.tech_laptop:before {
content: "\eb2f";
}
.nc-icon-mini.tech_mobile-button:before {
content: "\eb30";
}
.nc-icon-mini.tech_mobile-camera:before {
content: "\eb31";
}
.nc-icon-mini.tech_mobile-recharger-08:before {
content: "\eb32";
}
.nc-icon-mini.tech_mobile-recharger-09:before {
content: "\eb33";
}
.nc-icon-mini.tech_mobile-toolbar:before {
content: "\eb34";
}
.nc-icon-mini.tech_mobile:before {
content: "\eb35";
}
.nc-icon-mini.tech_music:before {
content: "\eb36";
}
.nc-icon-mini.tech_navigation:before {
content: "\eb37";
}
.nc-icon-mini.tech_player-19:before {
content: "\eb38";
}
.nc-icon-mini.tech_player-48:before {
content: "\eb39";
}
.nc-icon-mini.tech_print-round:before {
content: "\eb3a";
}
.nc-icon-mini.tech_print:before {
content: "\eb3b";
}
.nc-icon-mini.tech_ram:before {
content: "\eb3c";
}
.nc-icon-mini.tech_remote:before {
content: "\eb3d";
}
.nc-icon-mini.tech_signal:before {
content: "\eb3e";
}
.nc-icon-mini.tech_socket:before {
content: "\eb3f";
}
.nc-icon-mini.tech_sync:before {
content: "\eb40";
}
.nc-icon-mini.tech_tablet-button:before {
content: "\eb41";
}
.nc-icon-mini.tech_tablet-reader-31:before {
content: "\eb42";
}
.nc-icon-mini.tech_tablet-reader-42:before {
content: "\eb43";
}
.nc-icon-mini.tech_tablet-toolbar:before {
content: "\eb44";
}
.nc-icon-mini.tech_tablet:before {
content: "\eb45";
}
.nc-icon-mini.tech_tv-old:before {
content: "\eb46";
}
.nc-icon-mini.tech_tv:before {
content: "\eb47";
}
.nc-icon-mini.tech_watch-circle:before {
content: "\eb48";
}
.nc-icon-mini.tech_watch-time:before {
content: "\eb49";
}
.nc-icon-mini.tech_watch:before {
content: "\eb4a";
}
.nc-icon-mini.tech_webcam-38:before {
content: "\eb4b";
}
.nc-icon-mini.tech_webcam-39:before {
content: "\eb4c";
}
.nc-icon-mini.tech_wifi-router:before {
content: "\eb4d";
}
.nc-icon-mini.tech_wifi:before {
content: "\eb4e";
}
.nc-icon-mini.sport_badminton:before {
content: "\eb4f";
}
.nc-icon-mini.sport_baseball-ball:before {
content: "\eb50";
}
.nc-icon-mini.sport_baseball-bat:before {
content: "\eb51";
}
.nc-icon-mini.sport_basketball-12:before {
content: "\eb52";
}
.nc-icon-mini.sport_boxing:before {
content: "\eb53";
}
.nc-icon-mini.sport_cardio:before {
content: "\eb54";
}
.nc-icon-mini.sport_cricket:before {
content: "\eb55";
}
.nc-icon-mini.sport_crown:before {
content: "\eb56";
}
.nc-icon-mini.sport_dart:before {
content: "\eb57";
}
.nc-icon-mini.sport_dumbbells:before {
content: "\eb58";
}
.nc-icon-mini.sport_fencing:before {
content: "\eb59";
}
.nc-icon-mini.sport_fishing:before {
content: "\eb5a";
}
.nc-icon-mini.sport_flag-finish:before {
content: "\eb5b";
}
.nc-icon-mini.sport_football-headguard:before {
content: "\eb5c";
}
.nc-icon-mini.sport_golf:before {
content: "\eb5d";
}
.nc-icon-mini.sport_helmet:before {
content: "\eb5e";
}
.nc-icon-mini.sport_hockey:before {
content: "\eb5f";
}
.nc-icon-mini.sport_kettlebell:before {
content: "\eb60";
}
.nc-icon-mini.sport_ping-pong:before {
content: "\eb61";
}
.nc-icon-mini.sport_podium:before {
content: "\eb62";
}
.nc-icon-mini.sport_rope:before {
content: "\eb63";
}
.nc-icon-mini.sport_rugby:before {
content: "\eb64";
}
.nc-icon-mini.sport_shaker:before {
content: "\eb65";
}
.nc-icon-mini.sport_skateboard:before {
content: "\eb66";
}
.nc-icon-mini.sport_snowboard:before {
content: "\eb67";
}
.nc-icon-mini.sport_soccer-field:before {
content: "\eb68";
}
.nc-icon-mini.sport_steering-wheel:before {
content: "\eb69";
}
.nc-icon-mini.sport_supplement:before {
content: "\eb6a";
}
.nc-icon-mini.sport_surf:before {
content: "\eb6b";
}
.nc-icon-mini.sport_tactic:before {
content: "\eb6c";
}
.nc-icon-mini.sport_tennis-ball:before {
content: "\eb6d";
}
.nc-icon-mini.sport_tennis:before {
content: "\eb6e";
}
.nc-icon-mini.sport_trophy:before {
content: "\eb6f";
}
.nc-icon-mini.sport_user-run:before {
content: "\eb70";
}
.nc-icon-mini.sport_volleyball:before {
content: "\eb71";
}
.nc-icon-mini.sport_whistle:before {
content: "\eb72";
}
.nc-icon-mini.social_logo-500px:before {
content: "\eb73";
}
.nc-icon-mini.social_logo-angellist:before {
content: "\eb74";
}
.nc-icon-mini.social_logo-behance:before {
content: "\eb75";
}
.nc-icon-mini.social_logo-blogger:before {
content: "\eb76";
}
.nc-icon-mini.social_logo-buffer:before {
content: "\eb77";
}
.nc-icon-mini.social_logo-buysellads:before {
content: "\eb78";
}
.nc-icon-mini.social_logo-codepen:before {
content: "\eb79";
}
.nc-icon-mini.social_logo-creative-market:before {
content: "\eb7a";
}
.nc-icon-mini.social_logo-crunchbase:before {
content: "\eb7b";
}
.nc-icon-mini.social_logo-deviantart:before {
content: "\eb7c";
}
.nc-icon-mini.social_logo-dribbble:before {
content: "\eb7d";
}
.nc-icon-mini.social_logo-dropbox:before {
content: "\eb7e";
}
.nc-icon-mini.social_logo-envato:before {
content: "\eb7f";
}
.nc-icon-mini.social_logo-evernote:before {
content: "\eb80";
}
.nc-icon-mini.social_logo-facebook:before {
content: "\eb81";
}
.nc-icon-mini.social_logo-fb-simple:before {
content: "\eb82";
}
.nc-icon-mini.social_logo-feedly:before {
content: "\eb83";
}
.nc-icon-mini.social_logo-flickr:before {
content: "\eb84";
}
.nc-icon-mini.social_logo-github:before {
content: "\eb85";
}
.nc-icon-mini.social_logo-google-plus:before {
content: "\eb86";
}
.nc-icon-mini.social_logo-instagram:before {
content: "\eb87";
}
.nc-icon-mini.social_logo-lastfm:before {
content: "\eb88";
}
.nc-icon-mini.social_logo-linkedin:before {
content: "\eb89";
}
.nc-icon-mini.social_logo-meetup:before {
content: "\eb8a";
}
.nc-icon-mini.social_logo-myspace:before {
content: "\eb8b";
}
.nc-icon-mini.social_logo-paypal:before {
content: "\eb8c";
}
.nc-icon-mini.social_logo-pinterest:before {
content: "\eb8d";
}
.nc-icon-mini.social_logo-product-hunt:before {
content: "\eb8e";
}
.nc-icon-mini.social_logo-reddit:before {
content: "\eb8f";
}
.nc-icon-mini.social_logo-rss:before {
content: "\eb90";
}
.nc-icon-mini.social_logo-shopify:before {
content: "\eb91";
}
.nc-icon-mini.social_logo-skype:before {
content: "\eb92";
}
.nc-icon-mini.social_logo-slack:before {
content: "\eb93";
}
.nc-icon-mini.social_logo-soundcloud:before {
content: "\eb94";
}
.nc-icon-mini.social_logo-spotify:before {
content: "\eb95";
}
.nc-icon-mini.social_logo-trello:before {
content: "\eb96";
}
.nc-icon-mini.social_logo-tumblr:before {
content: "\eb97";
}
.nc-icon-mini.social_logo-twitter:before {
content: "\eb98";
}
.nc-icon-mini.social_logo-vimeo:before {
content: "\eb99";
}
.nc-icon-mini.social_logo-vine:before {
content: "\eb9a";
}
.nc-icon-mini.social_logo-whatsapp:before {
content: "\eb9b";
}
.nc-icon-mini.social_logo-wordpress:before {
content: "\eb9c";
}
.nc-icon-mini.social_logo-yelp:before {
content: "\eb9d";
}
.nc-icon-mini.social_logo-youtube:before {
content: "\eb9e";
}
.nc-icon-mini.shopping_award:before {
content: "\eb9f";
}
.nc-icon-mini.shopping_bag-09:before {
content: "\eba0";
}
.nc-icon-mini.shopping_bag-16:before {
content: "\eba1";
}
.nc-icon-mini.shopping_bag-17:before {
content: "\eba2";
}
.nc-icon-mini.shopping_bag-20:before {
content: "\eba3";
}
.nc-icon-mini.shopping_bag-add-18:before {
content: "\eba4";
}
.nc-icon-mini.shopping_bag-add-21:before {
content: "\eba5";
}
.nc-icon-mini.shopping_bag-remove-19:before {
content: "\eba6";
}
.nc-icon-mini.shopping_bag-remove-22:before {
content: "\eba7";
}
.nc-icon-mini.shopping_barcode:before {
content: "\eba8";
}
.nc-icon-mini.shopping_bardcode-qr:before {
content: "\eba9";
}
.nc-icon-mini.shopping_basket-simple-add:before {
content: "\ebaa";
}
.nc-icon-mini.shopping_basket-simple-remove:before {
content: "\ebab";
}
.nc-icon-mini.shopping_basket-simple:before {
content: "\ebac";
}
.nc-icon-mini.shopping_basket:before {
content: "\ebad";
}
.nc-icon-mini.shopping_bitcoin:before {
content: "\ebae";
}
.nc-icon-mini.shopping_box-ribbon:before {
content: "\ebaf";
}
.nc-icon-mini.shopping_box:before {
content: "\ebb0";
}
.nc-icon-mini.shopping_cart-modern-in:before {
content: "\ebb1";
}
.nc-icon-mini.shopping_cart-modern:before {
content: "\ebb2";
}
.nc-icon-mini.shopping_cart-simple-add:before {
content: "\ebb3";
}
.nc-icon-mini.shopping_cart-simple-in:before {
content: "\ebb4";
}
.nc-icon-mini.shopping_cart-simple-remove:before {
content: "\ebb5";
}
.nc-icon-mini.shopping_cart-simple:before {
content: "\ebb6";
}
.nc-icon-mini.shopping_cart:before {
content: "\ebb7";
}
.nc-icon-mini.shopping_cash-register:before {
content: "\ebb8";
}
.nc-icon-mini.shopping_chart:before {
content: "\ebb9";
}
.nc-icon-mini.shopping_credit-card-in:before {
content: "\ebba";
}
.nc-icon-mini.shopping_credit-card:before {
content: "\ebbb";
}
.nc-icon-mini.shopping_delivery-fast:before {
content: "\ebbc";
}
.nc-icon-mini.shopping_discount:before {
content: "\ebbd";
}
.nc-icon-mini.shopping_gift:before {
content: "\ebbe";
}
.nc-icon-mini.shopping_list:before {
content: "\ebbf";
}
.nc-icon-mini.shopping_newsletter:before {
content: "\ebc0";
}
.nc-icon-mini.shopping_receipt-list-42:before {
content: "\ebc1";
}
.nc-icon-mini.shopping_receipt-list-43:before {
content: "\ebc2";
}
.nc-icon-mini.shopping_receipt:before {
content: "\ebc3";
}
.nc-icon-mini.shopping_shop:before {
content: "\ebc4";
}
.nc-icon-mini.shopping_stock:before {
content: "\ebc5";
}
.nc-icon-mini.shopping_tag-content:before {
content: "\ebc6";
}
.nc-icon-mini.shopping_tag-cut:before {
content: "\ebc7";
}
.nc-icon-mini.shopping_tag:before {
content: "\ebc8";
}
.nc-icon-mini.shopping_wallet:before {
content: "\ebc9";
}
.nc-icon-mini.education_agenda-bookmark:before {
content: "\ebca";
}
.nc-icon-mini.education_atom:before {
content: "\ebcb";
}
.nc-icon-mini.education_award-55:before {
content: "\ebcc";
}
.nc-icon-mini.education_backpack-57:before {
content: "\ebcd";
}
.nc-icon-mini.education_backpack-58:before {
content: "\ebce";
}
.nc-icon-mini.education_ball-basket:before {
content: "\ebcf";
}
.nc-icon-mini.education_ball-soccer:before {
content: "\ebd0";
}
.nc-icon-mini.education_board-51:before {
content: "\ebd1";
}
.nc-icon-mini.education_book-39:before {
content: "\ebd2";
}
.nc-icon-mini.education_book-bookmark:before {
content: "\ebd3";
}
.nc-icon-mini.education_book-open:before {
content: "\ebd4";
}
.nc-icon-mini.education_books-46:before {
content: "\ebd5";
}
.nc-icon-mini.education_flask:before {
content: "\ebd6";
}
.nc-icon-mini.education_glasses:before {
content: "\ebd7";
}
.nc-icon-mini.education_hat:before {
content: "\ebd8";
}
.nc-icon-mini.education_language:before {
content: "\ebd9";
}
.nc-icon-mini.education_molecule:before {
content: "\ebda";
}
.nc-icon-mini.education_notepad:before {
content: "\ebdb";
}
.nc-icon-mini.education_paper-diploma:before {
content: "\ebdc";
}
.nc-icon-mini.education_paper:before {
content: "\ebdd";
}
.nc-icon-mini.education_pencil-47:before {
content: "\ebde";
}
.nc-icon-mini.education_school:before {
content: "\ebdf";
}
.nc-icon-mini.objects_alien-29:before {
content: "\ebe0";
}
.nc-icon-mini.objects_alien-33:before {
content: "\ebe1";
}
.nc-icon-mini.objects_anchor:before {
content: "\ebe2";
}
.nc-icon-mini.objects_astronaut:before {
content: "\ebe3";
}
.nc-icon-mini.objects_axe:before {
content: "\ebe4";
}
.nc-icon-mini.objects_baby-bottle:before {
content: "\ebe5";
}
.nc-icon-mini.objects_baby:before {
content: "\ebe6";
}
.nc-icon-mini.objects_baloon:before {
content: "\ebe7";
}
.nc-icon-mini.objects_battery:before {
content: "\ebe8";
}
.nc-icon-mini.objects_bear:before {
content: "\ebe9";
}
.nc-icon-mini.objects_billiard:before {
content: "\ebea";
}
.nc-icon-mini.objects_binocular:before {
content: "\ebeb";
}
.nc-icon-mini.objects_bow:before {
content: "\ebec";
}
.nc-icon-mini.objects_bowling:before {
content: "\ebed";
}
.nc-icon-mini.objects_broom:before {
content: "\ebee";
}
.nc-icon-mini.objects_cone:before {
content: "\ebef";
}
.nc-icon-mini.objects_controller:before {
content: "\ebf0";
}
.nc-icon-mini.objects_diamond:before {
content: "\ebf1";
}
.nc-icon-mini.objects_dice:before {
content: "\ebf2";
}
.nc-icon-mini.objects_globe:before {
content: "\ebf3";
}
.nc-icon-mini.objects_hut:before {
content: "\ebf4";
}
.nc-icon-mini.objects_key-25:before {
content: "\ebf5";
}
.nc-icon-mini.objects_key-26:before {
content: "\ebf6";
}
.nc-icon-mini.objects_lamp:before {
content: "\ebf7";
}
.nc-icon-mini.objects_leaf-36:before {
content: "\ebf8";
}
.nc-icon-mini.objects_light:before {
content: "\ebf9";
}
.nc-icon-mini.objects_planet:before {
content: "\ebfa";
}
.nc-icon-mini.objects_puzzle-09:before {
content: "\ebfb";
}
.nc-icon-mini.objects_puzzle-10:before {
content: "\ebfc";
}
.nc-icon-mini.objects_shovel:before {
content: "\ebfd";
}
.nc-icon-mini.objects_skull:before {
content: "\ebfe";
}
.nc-icon-mini.objects_spaceship:before {
content: "\ebff";
}
.nc-icon-mini.objects_support-16:before {
content: "\ec00";
}
.nc-icon-mini.objects_support-17:before {
content: "\ec01";
}
.nc-icon-mini.objects_umbrella-13:before {
content: "\ec02";
}
.nc-icon-mini.objects_umbrella-14:before {
content: "\ec03";
}
.nc-icon-mini.objects_wool-ball:before {
content: "\ec04";
}
.nc-icon-mini.media-1_3d:before {
content: "\ec05";
}
.nc-icon-mini.media-1_album:before {
content: "\ec06";
}
.nc-icon-mini.media-1_audio-91:before {
content: "\ec07";
}
.nc-icon-mini.media-1_audio-92:before {
content: "\ec08";
}
.nc-icon-mini.media-1_balance:before {
content: "\ec09";
}
.nc-icon-mini.media-1_brightness-46:before {
content: "\ec0a";
}
.nc-icon-mini.media-1_brightness-47:before {
content: "\ec0b";
}
.nc-icon-mini.media-1_button-eject:before {
content: "\ec0c";
}
.nc-icon-mini.media-1_button-next:before {
content: "\ec0d";
}
.nc-icon-mini.media-1_button-pause:before {
content: "\ec0e";
}
.nc-icon-mini.media-1_button-play:before {
content: "\ec0f";
}
.nc-icon-mini.media-1_button-power:before {
content: "\ec10";
}
.nc-icon-mini.media-1_button-previous:before {
content: "\ec11";
}
.nc-icon-mini.media-1_button-record:before {
content: "\ec12";
}
.nc-icon-mini.media-1_button-rewind:before {
content: "\ec13";
}
.nc-icon-mini.media-1_button-skip:before {
content: "\ec14";
}
.nc-icon-mini.media-1_button-stop:before {
content: "\ec15";
}
.nc-icon-mini.media-1_camera-18:before {
content: "\ec16";
}
.nc-icon-mini.media-1_camera-19:before {
content: "\ec17";
}
.nc-icon-mini.media-1_camera-20:before {
content: "\ec18";
}
.nc-icon-mini.media-1_camera-ban-37:before {
content: "\ec19";
}
.nc-icon-mini.media-1_camera-compact:before {
content: "\ec1a";
}
.nc-icon-mini.media-1_camera-square-57:before {
content: "\ec1b";
}
.nc-icon-mini.media-1_camera-time:before {
content: "\ec1c";
}
.nc-icon-mini.media-1_countdown-34:before {
content: "\ec1d";
}
.nc-icon-mini.media-1_edit-color:before {
content: "\ec1e";
}
.nc-icon-mini.media-1_edit-contrast-42:before {
content: "\ec1f";
}
.nc-icon-mini.media-1_edit-contrast-43:before {
content: "\ec20";
}
.nc-icon-mini.media-1_edit-saturation:before {
content: "\ec21";
}
.nc-icon-mini.media-1_flash-21:before {
content: "\ec22";
}
.nc-icon-mini.media-1_flash-24:before {
content: "\ec23";
}
.nc-icon-mini.media-1_flash-29:before {
content: "\ec24";
}
.nc-icon-mini.media-1_focus-32:before {
content: "\ec25";
}
.nc-icon-mini.media-1_focus-38:before {
content: "\ec26";
}
.nc-icon-mini.media-1_focus-40:before {
content: "\ec27";
}
.nc-icon-mini.media-1_focus-circle:before {
content: "\ec28";
}
.nc-icon-mini.media-1_frame-12:before {
content: "\ec29";
}
.nc-icon-mini.media-1_frame-41:before {
content: "\ec2a";
}
.nc-icon-mini.media-1_grid:before {
content: "\ec2b";
}
.nc-icon-mini.media-1_image-01:before {
content: "\ec2c";
}
.nc-icon-mini.media-1_image-02:before {
content: "\ec2d";
}
.nc-icon-mini.media-1_image-05:before {
content: "\ec2e";
}
.nc-icon-mini.media-1_layers:before {
content: "\ec2f";
}
.nc-icon-mini.media-1_lens-31:before {
content: "\ec30";
}
.nc-icon-mini.media-1_lens-56:before {
content: "\ec31";
}
.nc-icon-mini.media-1_macro:before {
content: "\ec32";
}
.nc-icon-mini.media-1_movie-61:before {
content: "\ec33";
}
.nc-icon-mini.media-1_movie-62:before {
content: "\ec34";
}
.nc-icon-mini.media-1_night:before {
content: "\ec35";
}
.nc-icon-mini.media-1_picture:before {
content: "\ec36";
}
.nc-icon-mini.media-1_play-68:before {
content: "\ec37";
}
.nc-icon-mini.media-1_play-69:before {
content: "\ec38";
}
.nc-icon-mini.media-1_player:before {
content: "\ec39";
}
.nc-icon-mini.media-1_polaroid-add:before {
content: "\ec3a";
}
.nc-icon-mini.media-1_polaroid-delete:before {
content: "\ec3b";
}
.nc-icon-mini.media-1_polaroid-multiple:before {
content: "\ec3c";
}
.nc-icon-mini.media-1_polaroid:before {
content: "\ec3d";
}
.nc-icon-mini.media-1_roll:before {
content: "\ec3e";
}
.nc-icon-mini.media-1_sd:before {
content: "\ec3f";
}
.nc-icon-mini.media-1_ticket-75:before {
content: "\ec40";
}
.nc-icon-mini.media-1_ticket-76:before {
content: "\ec41";
}
.nc-icon-mini.media-1_touch:before {
content: "\ec42";
}
.nc-icon-mini.media-1_tripod:before {
content: "\ec43";
}
.nc-icon-mini.media-1_video-64:before {
content: "\ec44";
}
.nc-icon-mini.media-1_video-65:before {
content: "\ec45";
}
.nc-icon-mini.media-1_video-66:before {
content: "\ec46";
}
.nc-icon-mini.media-1_video-67:before {
content: "\ec47";
}
.nc-icon-mini.media-1_videocamera-71:before {
content: "\ec48";
}
.nc-icon-mini.media-1_videocamera-72:before {
content: "\ec49";
}
.nc-icon-mini.media-1_volume-93:before {
content: "\ec4a";
}
.nc-icon-mini.media-1_volume-97:before {
content: "\ec4b";
}
.nc-icon-mini.media-1_volume-down:before {
content: "\ec4c";
}
.nc-icon-mini.media-1_volume-up:before {
content: "\ec4d";
}
.nc-icon-mini.media-2_headphones-mic:before {
content: "\ec4e";
}
.nc-icon-mini.media-2_headphones:before {
content: "\ec4f";
}
.nc-icon-mini.media-2_knob:before {
content: "\ec50";
}
.nc-icon-mini.media-2_mic:before {
content: "\ec51";
}
.nc-icon-mini.media-2_music-album:before {
content: "\ec52";
}
.nc-icon-mini.media-2_note-03:before {
content: "\ec53";
}
.nc-icon-mini.media-2_note-04:before {
content: "\ec54";
}
.nc-icon-mini.media-2_radio:before {
content: "\ec55";
}
.nc-icon-mini.media-2_sound-wave:before {
content: "\ec56";
}
.nc-icon-mini.media-2_speaker-01:before {
content: "\ec57";
}
.nc-icon-mini.media-2_speaker-05:before {
content: "\ec58";
}
.nc-icon-mini.media-2_tape:before {
content: "\ec59";
}
.nc-icon-mini.location_appointment:before {
content: "\ec5a";
}
.nc-icon-mini.location_bookmark-add:before {
content: "\ec5b";
}
.nc-icon-mini.location_bookmark-remove:before {
content: "\ec5c";
}
.nc-icon-mini.location_bookmark:before {
content: "\ec5d";
}
.nc-icon-mini.location_compass-04:before {
content: "\ec5e";
}
.nc-icon-mini.location_compass-05:before {
content: "\ec5f";
}
.nc-icon-mini.location_compass-06:before {
content: "\ec60";
}
.nc-icon-mini.location_crosshair:before {
content: "\ec61";
}
.nc-icon-mini.location_explore:before {
content: "\ec62";
}
.nc-icon-mini.location_flag-diagonal-33:before {
content: "\ec63";
}
.nc-icon-mini.location_flag-diagonal-34:before {
content: "\ec64";
}
.nc-icon-mini.location_flag-points-31:before {
content: "\ec65";
}
.nc-icon-mini.location_flag-points-32:before {
content: "\ec66";
}
.nc-icon-mini.location_flag-simple:before {
content: "\ec67";
}
.nc-icon-mini.location_flag-triangle:before {
content: "\ec68";
}
.nc-icon-mini.location_flag:before {
content: "\ec69";
}
.nc-icon-mini.location_gps:before {
content: "\ec6a";
}
.nc-icon-mini.location_map:before {
content: "\ec6b";
}
.nc-icon-mini.location_marker:before {
content: "\ec6c";
}
.nc-icon-mini.location_pin-add:before {
content: "\ec6d";
}
.nc-icon-mini.location_pin-copy:before {
content: "\ec6e";
}
.nc-icon-mini.location_pin-remove:before {
content: "\ec6f";
}
.nc-icon-mini.location_pin:before {
content: "\ec70";
}
.nc-icon-mini.location_pins:before {
content: "\ec71";
}
.nc-icon-mini.location_position-marker:before {
content: "\ec72";
}
.nc-icon-mini.location_radar:before {
content: "\ec73";
}
.nc-icon-mini.location_treasure-map-40:before {
content: "\ec74";
}
.nc-icon-mini.location_world:before {
content: "\ec75";
}
.nc-icon-mini.location-e_bookmark-add:before {
content: "\ec76";
}
.nc-icon-mini.location-e_bookmark-remove:before {
content: "\ec77";
}
.nc-icon-mini.location-e_flag-06:before {
content: "\ec78";
}
.nc-icon-mini.location-e_flag-07:before {
content: "\ec79";
}
.nc-icon-mini.location-e_gps:before {
content: "\ec7a";
}
.nc-icon-mini.location-e_pin-add:before {
content: "\ec7b";
}
.nc-icon-mini.location-e_pin-remove:before {
content: "\ec7c";
}
.nc-icon-mini.location-e_pin:before {
content: "\ec7d";
}
.nc-icon-mini.holidays_bat:before {
content: "\ec7e";
}
.nc-icon-mini.holidays_bones:before {
content: "\ec7f";
}
.nc-icon-mini.holidays_boot:before {
content: "\ec80";
}
.nc-icon-mini.holidays_candy:before {
content: "\ec81";
}
.nc-icon-mini.holidays_cat:before {
content: "\ec82";
}
.nc-icon-mini.holidays_cauldron:before {
content: "\ec83";
}
.nc-icon-mini.holidays_chimney:before {
content: "\ec84";
}
.nc-icon-mini.holidays_cockade:before {
content: "\ec85";
}
.nc-icon-mini.holidays_coffin:before {
content: "\ec86";
}
.nc-icon-mini.holidays_deer:before {
content: "\ec87";
}
.nc-icon-mini.holidays_egg-38:before {
content: "\ec88";
}
.nc-icon-mini.holidays_egg-39:before {
content: "\ec89";
}
.nc-icon-mini.holidays_ghost:before {
content: "\ec8a";
}
.nc-icon-mini.holidays_gift:before {
content: "\ec8b";
}
.nc-icon-mini.holidays_glove:before {
content: "\ec8c";
}
.nc-icon-mini.holidays_grave:before {
content: "\ec8d";
}
.nc-icon-mini.holidays_light:before {
content: "\ec8e";
}
.nc-icon-mini.holidays_message:before {
content: "\ec8f";
}
.nc-icon-mini.holidays_owl:before {
content: "\ec90";
}
.nc-icon-mini.holidays_pumpkin:before {
content: "\ec91";
}
.nc-icon-mini.holidays_rabbit:before {
content: "\ec92";
}
.nc-icon-mini.holidays_santa-hat:before {
content: "\ec93";
}
.nc-icon-mini.holidays_sickle:before {
content: "\ec94";
}
.nc-icon-mini.holidays_snow-ball:before {
content: "\ec95";
}
.nc-icon-mini.holidays_snowman:before {
content: "\ec96";
}
.nc-icon-mini.holidays_soak:before {
content: "\ec97";
}
.nc-icon-mini.holidays_spider:before {
content: "\ec98";
}
.nc-icon-mini.holidays_tree-ball:before {
content: "\ec99";
}
.nc-icon-mini.holidays_tree:before {
content: "\ec9a";
}
.nc-icon-mini.holidays_vampire:before {
content: "\ec9b";
}
.nc-icon-mini.holidays_witch-hat:before {
content: "\ec9c";
}
.nc-icon-mini.holidays_wolf:before {
content: "\ec9d";
}
.nc-icon-mini.holidays_zombie:before {
content: "\ec9e";
}
.nc-icon-mini.health_apple:before {
content: "\ec9f";
}
.nc-icon-mini.health_bag-49:before {
content: "\eca0";
}
.nc-icon-mini.health_bag-50:before {
content: "\eca1";
}
.nc-icon-mini.health_brain:before {
content: "\eca2";
}
.nc-icon-mini.health_dna-27:before {
content: "\eca3";
}
.nc-icon-mini.health_dna-38:before {
content: "\eca4";
}
.nc-icon-mini.health_flask:before {
content: "\eca5";
}
.nc-icon-mini.health_heartbeat-16:before {
content: "\eca6";
}
.nc-icon-mini.health_height:before {
content: "\eca7";
}
.nc-icon-mini.health_hospital-32:before {
content: "\eca8";
}
.nc-icon-mini.health_hospital-33:before {
content: "\eca9";
}
.nc-icon-mini.health_hospital-34:before {
content: "\ecaa";
}
.nc-icon-mini.health_humidity-26:before {
content: "\ecab";
}
.nc-icon-mini.health_humidity-52:before {
content: "\ecac";
}
.nc-icon-mini.health_molecule-39:before {
content: "\ecad";
}
.nc-icon-mini.health_notebook:before {
content: "\ecae";
}
.nc-icon-mini.health_patch-46:before {
content: "\ecaf";
}
.nc-icon-mini.health_pill-42:before {
content: "\ecb0";
}
.nc-icon-mini.health_pill-43:before {
content: "\ecb1";
}
.nc-icon-mini.health_pill-container-44:before {
content: "\ecb2";
}
.nc-icon-mini.health_pill-container-47:before {
content: "\ecb3";
}
.nc-icon-mini.health_pulse-chart:before {
content: "\ecb4";
}
.nc-icon-mini.health_pulse-sleep:before {
content: "\ecb5";
}
.nc-icon-mini.health_pulse:before {
content: "\ecb6";
}
.nc-icon-mini.health_steps:before {
content: "\ecb7";
}
.nc-icon-mini.health_syringe:before {
content: "\ecb8";
}
.nc-icon-mini.health_temperature-23:before {
content: "\ecb9";
}
.nc-icon-mini.health_temperature-24:before {
content: "\ecba";
}
.nc-icon-mini.health_tooth:before {
content: "\ecbb";
}
.nc-icon-mini.health_weed:before {
content: "\ecbc";
}
.nc-icon-mini.health_weight:before {
content: "\ecbd";
}
.nc-icon-mini.health_wheelchair:before {
content: "\ecbe";
}
.nc-icon-mini.health_woman:before {
content: "\ecbf";
}
.nc-icon-mini.furniture_air-conditioner:before {
content: "\ecc0";
}
.nc-icon-mini.furniture_armchair:before {
content: "\ecc1";
}
.nc-icon-mini.furniture_bed-09:before {
content: "\ecc2";
}
.nc-icon-mini.furniture_bed-23:before {
content: "\ecc3";
}
.nc-icon-mini.furniture_cabinet:before {
content: "\ecc4";
}
.nc-icon-mini.furniture_cactus:before {
content: "\ecc5";
}
.nc-icon-mini.furniture_chair:before {
content: "\ecc6";
}
.nc-icon-mini.furniture_coat-hanger:before {
content: "\ecc7";
}
.nc-icon-mini.furniture_coffee:before {
content: "\ecc8";
}
.nc-icon-mini.furniture_cradle:before {
content: "\ecc9";
}
.nc-icon-mini.furniture_curtain:before {
content: "\ecca";
}
.nc-icon-mini.furniture_desk:before {
content: "\eccb";
}
.nc-icon-mini.furniture_door:before {
content: "\eccc";
}
.nc-icon-mini.furniture_drawer:before {
content: "\eccd";
}
.nc-icon-mini.furniture_fridge:before {
content: "\ecce";
}
.nc-icon-mini.furniture_hanger:before {
content: "\eccf";
}
.nc-icon-mini.furniture_iron:before {
content: "\ecd0";
}
.nc-icon-mini.furniture_lamp-floor:before {
content: "\ecd1";
}
.nc-icon-mini.furniture_lamp:before {
content: "\ecd2";
}
.nc-icon-mini.furniture_library:before {
content: "\ecd3";
}
.nc-icon-mini.furniture_light:before {
content: "\ecd4";
}
.nc-icon-mini.furniture_mixer:before {
content: "\ecd5";
}
.nc-icon-mini.furniture_oven:before {
content: "\ecd6";
}
.nc-icon-mini.furniture_shower:before {
content: "\ecd7";
}
.nc-icon-mini.furniture_sink-wash:before {
content: "\ecd8";
}
.nc-icon-mini.furniture_sink:before {
content: "\ecd9";
}
.nc-icon-mini.furniture_storage-hanger:before {
content: "\ecda";
}
.nc-icon-mini.furniture_storage:before {
content: "\ecdb";
}
.nc-icon-mini.furniture_toilet-paper:before {
content: "\ecdc";
}
.nc-icon-mini.furniture_toilet:before {
content: "\ecdd";
}
.nc-icon-mini.furniture_tv:before {
content: "\ecde";
}
.nc-icon-mini.furniture_wardrobe:before {
content: "\ecdf";
}
.nc-icon-mini.furniture_wash:before {
content: "\ece0";
}
.nc-icon-mini.food_baby:before {
content: "\ece1";
}
.nc-icon-mini.food_bacon:before {
content: "\ece2";
}
.nc-icon-mini.food_banana:before {
content: "\ece3";
}
.nc-icon-mini.food_barbecue-tools:before {
content: "\ece4";
}
.nc-icon-mini.food_beer-95:before {
content: "\ece5";
}
.nc-icon-mini.food_beer-96:before {
content: "\ece6";
}
.nc-icon-mini.food_beverage:before {
content: "\ece7";
}
.nc-icon-mini.food_bottle-wine:before {
content: "\ece8";
}
.nc-icon-mini.food_bottle:before {
content: "\ece9";
}
.nc-icon-mini.food_bowl:before {
content: "\ecea";
}
.nc-icon-mini.food_bread:before {
content: "\eceb";
}
.nc-icon-mini.food_broccoli:before {
content: "\ecec";
}
.nc-icon-mini.food_cake-13:before {
content: "\eced";
}
.nc-icon-mini.food_cake-slice:before {
content: "\ecee";
}
.nc-icon-mini.food_candle:before {
content: "\ecef";
}
.nc-icon-mini.food_candy:before {
content: "\ecf0";
}
.nc-icon-mini.food_carrot:before {
content: "\ecf1";
}
.nc-icon-mini.food_champagne:before {
content: "\ecf2";
}
.nc-icon-mini.food_cheese-24:before {
content: "\ecf3";
}
.nc-icon-mini.food_cheese-87:before {
content: "\ecf4";
}
.nc-icon-mini.food_cheeseburger:before {
content: "\ecf5";
}
.nc-icon-mini.food_chef-hat:before {
content: "\ecf6";
}
.nc-icon-mini.food_cherry:before {
content: "\ecf7";
}
.nc-icon-mini.food_chicken:before {
content: "\ecf8";
}
.nc-icon-mini.food_chili:before {
content: "\ecf9";
}
.nc-icon-mini.food_chinese:before {
content: "\ecfa";
}
.nc-icon-mini.food_chips:before {
content: "\ecfb";
}
.nc-icon-mini.food_chocolate:before {
content: "\ecfc";
}
.nc-icon-mini.food_cocktail:before {
content: "\ecfd";
}
.nc-icon-mini.food_coffe-long:before {
content: "\ecfe";
}
.nc-icon-mini.food_coffee-long:before {
content: "\ecff";
}
.nc-icon-mini.food_coffee:before {
content: "\ed00";
}
.nc-icon-mini.food_cookies:before {
content: "\ed01";
}
.nc-icon-mini.food_course:before {
content: "\ed02";
}
.nc-icon-mini.food_crab:before {
content: "\ed03";
}
.nc-icon-mini.food_croissant:before {
content: "\ed04";
}
.nc-icon-mini.food_cutlery-75:before {
content: "\ed05";
}
.nc-icon-mini.food_cutlery-76:before {
content: "\ed06";
}
.nc-icon-mini.food_cutlery-77:before {
content: "\ed07";
}
.nc-icon-mini.food_donut:before {
content: "\ed08";
}
.nc-icon-mini.food_drink:before {
content: "\ed09";
}
.nc-icon-mini.food_egg:before {
content: "\ed0a";
}
.nc-icon-mini.food_energy-drink:before {
content: "\ed0b";
}
.nc-icon-mini.food_fish:before {
content: "\ed0c";
}
.nc-icon-mini.food_glass:before {
content: "\ed0d";
}
.nc-icon-mini.food_grape:before {
content: "\ed0e";
}
.nc-icon-mini.food_hob:before {
content: "\ed0f";
}
.nc-icon-mini.food_hot-dog:before {
content: "\ed10";
}
.nc-icon-mini.food_ice-cream-22:before {
content: "\ed11";
}
.nc-icon-mini.food_ice-cream-72:before {
content: "\ed12";
}
.nc-icon-mini.food_kettle:before {
content: "\ed13";
}
.nc-icon-mini.food_knife:before {
content: "\ed14";
}
.nc-icon-mini.food_lighter:before {
content: "\ed15";
}
.nc-icon-mini.food_matches:before {
content: "\ed16";
}
.nc-icon-mini.food_measuring-cup:before {
content: "\ed17";
}
.nc-icon-mini.food_microwave:before {
content: "\ed18";
}
.nc-icon-mini.food_milk:before {
content: "\ed19";
}
.nc-icon-mini.food_moka:before {
content: "\ed1a";
}
.nc-icon-mini.food_muffin:before {
content: "\ed1b";
}
.nc-icon-mini.food_mug:before {
content: "\ed1c";
}
.nc-icon-mini.food_pan:before {
content: "\ed1d";
}
.nc-icon-mini.food_pizza-slice:before {
content: "\ed1e";
}
.nc-icon-mini.food_plate:before {
content: "\ed1f";
}
.nc-icon-mini.food_pot:before {
content: "\ed20";
}
.nc-icon-mini.food_recipe-book-46:before {
content: "\ed21";
}
.nc-icon-mini.food_recipe-book-47:before {
content: "\ed22";
}
.nc-icon-mini.food_rolling-pin:before {
content: "\ed23";
}
.nc-icon-mini.food_sausage:before {
content: "\ed24";
}
.nc-icon-mini.food_scale:before {
content: "\ed25";
}
.nc-icon-mini.food_steak:before {
content: "\ed26";
}
.nc-icon-mini.food_strawberry:before {
content: "\ed27";
}
.nc-icon-mini.food_sushi:before {
content: "\ed28";
}
.nc-icon-mini.food_tacos:before {
content: "\ed29";
}
.nc-icon-mini.food_tea:before {
content: "\ed2a";
}
.nc-icon-mini.food_watermelon:before {
content: "\ed2b";
}
.nc-icon-mini.food_whisk:before {
content: "\ed2c";
}
.nc-icon-mini.files_add:before {
content: "\ed2d";
}
.nc-icon-mini.files_archive-3d-content:before {
content: "\ed2e";
}
.nc-icon-mini.files_archive-check:before {
content: "\ed2f";
}
.nc-icon-mini.files_archive-content:before {
content: "\ed30";
}
.nc-icon-mini.files_archive-paper:before {
content: "\ed31";
}
.nc-icon-mini.files_archive:before {
content: "\ed32";
}
.nc-icon-mini.files_book-07:before {
content: "\ed33";
}
.nc-icon-mini.files_box:before {
content: "\ed34";
}
.nc-icon-mini.files_copy:before {
content: "\ed35";
}
.nc-icon-mini.files_drawer:before {
content: "\ed36";
}
.nc-icon-mini.files_folder-13:before {
content: "\ed37";
}
.nc-icon-mini.files_folder-14:before {
content: "\ed38";
}
.nc-icon-mini.files_folder-15:before {
content: "\ed39";
}
.nc-icon-mini.files_folder-16:before {
content: "\ed3a";
}
.nc-icon-mini.files_folder-17:before {
content: "\ed3b";
}
.nc-icon-mini.files_folder-18:before {
content: "\ed3c";
}
.nc-icon-mini.files_folder-add:before {
content: "\ed3d";
}
.nc-icon-mini.files_folder-remove:before {
content: "\ed3e";
}
.nc-icon-mini.files_notebook:before {
content: "\ed3f";
}
.nc-icon-mini.files_paper:before {
content: "\ed40";
}
.nc-icon-mini.files_remove:before {
content: "\ed41";
}
.nc-icon-mini.files_single-content-02:before {
content: "\ed42";
}
.nc-icon-mini.files_single-content-03:before {
content: "\ed43";
}
.nc-icon-mini.files_single-copies:before {
content: "\ed44";
}
.nc-icon-mini.files_single-copy-04:before {
content: "\ed45";
}
.nc-icon-mini.files_single-copy-06:before {
content: "\ed46";
}
.nc-icon-mini.files_single-folded-content:before {
content: "\ed47";
}
.nc-icon-mini.files_single-folded:before {
content: "\ed48";
}
.nc-icon-mini.files_single-paragraph:before {
content: "\ed49";
}
.nc-icon-mini.files_single:before {
content: "\ed4a";
}
.nc-icon-mini.files-e_add:before {
content: "\ed4b";
}
.nc-icon-mini.files-e_book:before {
content: "\ed4c";
}
.nc-icon-mini.files-e_folder-02:before {
content: "\ed4d";
}
.nc-icon-mini.files-e_folder-03:before {
content: "\ed4e";
}
.nc-icon-mini.files-e_folder-add:before {
content: "\ed4f";
}
.nc-icon-mini.files-e_folder-remove:before {
content: "\ed50";
}
.nc-icon-mini.files-e_remove:before {
content: "\ed51";
}
.nc-icon-mini.emoticons_angry-10:before {
content: "\ed52";
}
.nc-icon-mini.emoticons_angry-44:before {
content: "\ed53";
}
.nc-icon-mini.emoticons_big-eyes:before {
content: "\ed54";
}
.nc-icon-mini.emoticons_big-smile:before {
content: "\ed55";
}
.nc-icon-mini.emoticons_bigmouth:before {
content: "\ed56";
}
.nc-icon-mini.emoticons_bomb:before {
content: "\ed57";
}
.nc-icon-mini.emoticons_cake:before {
content: "\ed58";
}
.nc-icon-mini.emoticons_cry-15:before {
content: "\ed59";
}
.nc-icon-mini.emoticons_cute:before {
content: "\ed5a";
}
.nc-icon-mini.emoticons_devil:before {
content: "\ed5b";
}
.nc-icon-mini.emoticons_fist:before {
content: "\ed5c";
}
.nc-icon-mini.emoticons_ghost:before {
content: "\ed5d";
}
.nc-icon-mini.emoticons_happy-sun:before {
content: "\ed5e";
}
.nc-icon-mini.emoticons_kiss:before {
content: "\ed5f";
}
.nc-icon-mini.emoticons_laugh-35:before {
content: "\ed60";
}
.nc-icon-mini.emoticons_like-no:before {
content: "\ed61";
}
.nc-icon-mini.emoticons_like:before {
content: "\ed62";
}
.nc-icon-mini.emoticons_manga-62:before {
content: "\ed63";
}
.nc-icon-mini.emoticons_manga-63:before {
content: "\ed64";
}
.nc-icon-mini.emoticons_monster:before {
content: "\ed65";
}
.nc-icon-mini.emoticons_nerd-22:before {
content: "\ed66";
}
.nc-icon-mini.emoticons_poop:before {
content: "\ed67";
}
.nc-icon-mini.emoticons_puzzled:before {
content: "\ed68";
}
.nc-icon-mini.emoticons_quite-happy:before {
content: "\ed69";
}
.nc-icon-mini.emoticons_robot:before {
content: "\ed6a";
}
.nc-icon-mini.emoticons_sad:before {
content: "\ed6b";
}
.nc-icon-mini.emoticons_satisfied:before {
content: "\ed6c";
}
.nc-icon-mini.emoticons_shark:before {
content: "\ed6d";
}
.nc-icon-mini.emoticons_shy:before {
content: "\ed6e";
}
.nc-icon-mini.emoticons_skull:before {
content: "\ed6f";
}
.nc-icon-mini.emoticons_smile:before {
content: "\ed70";
}
.nc-icon-mini.emoticons_speechless:before {
content: "\ed71";
}
.nc-icon-mini.emoticons_sunglasses-48:before {
content: "\ed72";
}
.nc-icon-mini.emoticons_sunglasses-49:before {
content: "\ed73";
}
.nc-icon-mini.emoticons_surprise:before {
content: "\ed74";
}
.nc-icon-mini.emoticons_virus:before {
content: "\ed75";
}
.nc-icon-mini.emoticons_what:before {
content: "\ed76";
}
.nc-icon-mini.emoticons-e_angry-e-08:before {
content: "\ed77";
}
.nc-icon-mini.emoticons-e_angry-e-17:before {
content: "\ed78";
}
.nc-icon-mini.emoticons-e_ghost-e:before {
content: "\ed79";
}
.nc-icon-mini.emoticons-e_kiss-e:before {
content: "\ed7a";
}
.nc-icon-mini.emoticons-e_laugh-e:before {
content: "\ed7b";
}
.nc-icon-mini.emoticons-e_monster-e:before {
content: "\ed7c";
}
.nc-icon-mini.emoticons-e_puzzled-e:before {
content: "\ed7d";
}
.nc-icon-mini.emoticons-e_quite-happy-e:before {
content: "\ed7e";
}
.nc-icon-mini.emoticons-e_robot-e:before {
content: "\ed7f";
}
.nc-icon-mini.emoticons-e_sad-e:before {
content: "\ed80";
}
.nc-icon-mini.emoticons-e_satisfied-e:before {
content: "\ed81";
}
.nc-icon-mini.emoticons-e_shark-e:before {
content: "\ed82";
}
.nc-icon-mini.emoticons-e_shy-e:before {
content: "\ed83";
}
.nc-icon-mini.emoticons-e_smile-e:before {
content: "\ed84";
}
.nc-icon-mini.emoticons-e_speechless-e:before {
content: "\ed85";
}
.nc-icon-mini.emoticons-e_surprise-e:before {
content: "\ed86";
}
.nc-icon-mini.emoticons-e_what-e:before {
content: "\ed87";
}
.nc-icon-mini.design_album:before {
content: "\ed88";
}
.nc-icon-mini.design_align-bottom:before {
content: "\ed89";
}
.nc-icon-mini.design_align-center-horizontal:before {
content: "\ed8a";
}
.nc-icon-mini.design_align-center-vertical:before {
content: "\ed8b";
}
.nc-icon-mini.design_align-left:before {
content: "\ed8c";
}
.nc-icon-mini.design_align-right:before {
content: "\ed8d";
}
.nc-icon-mini.design_align-top:before {
content: "\ed8e";
}
.nc-icon-mini.design_app:before {
content: "\ed8f";
}
.nc-icon-mini.design_artboard:before {
content: "\ed90";
}
.nc-icon-mini.design_blend:before {
content: "\ed91";
}
.nc-icon-mini.design_book-bookmark:before {
content: "\ed92";
}
.nc-icon-mini.design_book-open:before {
content: "\ed93";
}
.nc-icon-mini.design_brush:before {
content: "\ed94";
}
.nc-icon-mini.design_bug:before {
content: "\ed95";
}
.nc-icon-mini.design_bullet-list-67:before {
content: "\ed96";
}
.nc-icon-mini.design_bullet-list-68:before {
content: "\ed97";
}
.nc-icon-mini.design_bullet-list-69:before {
content: "\ed98";
}
.nc-icon-mini.design_bullet-list-70:before {
content: "\ed99";
}
.nc-icon-mini.design_clone:before {
content: "\ed9a";
}
.nc-icon-mini.design_code:before {
content: "\ed9b";
}
.nc-icon-mini.design_collection:before {
content: "\ed9c";
}
.nc-icon-mini.design_command:before {
content: "\ed9d";
}
.nc-icon-mini.design_compass:before {
content: "\ed9e";
}
.nc-icon-mini.design_contrast:before {
content: "\ed9f";
}
.nc-icon-mini.design_copy:before {
content: "\eda0";
}
.nc-icon-mini.design_crop:before {
content: "\eda1";
}
.nc-icon-mini.design_cursor-48:before {
content: "\eda2";
}
.nc-icon-mini.design_cursor-49:before {
content: "\eda3";
}
.nc-icon-mini.design_design:before {
content: "\eda4";
}
.nc-icon-mini.design_distribute-horizontal:before {
content: "\eda5";
}
.nc-icon-mini.design_distribute-vertical:before {
content: "\eda6";
}
.nc-icon-mini.design_eraser-32:before {
content: "\eda7";
}
.nc-icon-mini.design_eraser-33:before {
content: "\eda8";
}
.nc-icon-mini.design_eraser-46:before {
content: "\eda9";
}
.nc-icon-mini.design_flip-horizontal:before {
content: "\edaa";
}
.nc-icon-mini.design_flip-vertical:before {
content: "\edab";
}
.nc-icon-mini.design_image:before {
content: "\edac";
}
.nc-icon-mini.design_magnet:before {
content: "\edad";
}
.nc-icon-mini.design_marker:before {
content: "\edae";
}
.nc-icon-mini.design_measure-02:before {
content: "\edaf";
}
.nc-icon-mini.design_measure-17:before {
content: "\edb0";
}
.nc-icon-mini.design_measure-big:before {
content: "\edb1";
}
.nc-icon-mini.design_mouse-08:before {
content: "\edb2";
}
.nc-icon-mini.design_mouse-09:before {
content: "\edb3";
}
.nc-icon-mini.design_mouse-10:before {
content: "\edb4";
}
.nc-icon-mini.design_note-code:before {
content: "\edb5";
}
.nc-icon-mini.design_paint-16:before {
content: "\edb6";
}
.nc-icon-mini.design_paint-37:before {
content: "\edb7";
}
.nc-icon-mini.design_paint-38:before {
content: "\edb8";
}
.nc-icon-mini.design_paint-bucket-39:before {
content: "\edb9";
}
.nc-icon-mini.design_paint-bucket-40:before {
content: "\edba";
}
.nc-icon-mini.design_palette:before {
content: "\edbb";
}
.nc-icon-mini.design_pantone:before {
content: "\edbc";
}
.nc-icon-mini.design_patch-19:before {
content: "\edbd";
}
.nc-icon-mini.design_patch-34:before {
content: "\edbe";
}
.nc-icon-mini.design_path-exclude:before {
content: "\edbf";
}
.nc-icon-mini.design_path-intersect:before {
content: "\edc0";
}
.nc-icon-mini.design_path-minus:before {
content: "\edc1";
}
.nc-icon-mini.design_path-unite:before {
content: "\edc2";
}
.nc-icon-mini.design_pen-01:before {
content: "\edc3";
}
.nc-icon-mini.design_pen-23:before {
content: "\edc4";
}
.nc-icon-mini.design_pen-tool:before {
content: "\edc5";
}
.nc-icon-mini.design_phone:before {
content: "\edc6";
}
.nc-icon-mini.design_scissors:before {
content: "\edc7";
}
.nc-icon-mini.design_shape-adjust:before {
content: "\edc8";
}
.nc-icon-mini.design_shape-circle:before {
content: "\edc9";
}
.nc-icon-mini.design_shape-polygon:before {
content: "\edca";
}
.nc-icon-mini.design_shape-square:before {
content: "\edcb";
}
.nc-icon-mini.design_shape-triangle:before {
content: "\edcc";
}
.nc-icon-mini.design_shapes:before {
content: "\edcd";
}
.nc-icon-mini.design_sharpener:before {
content: "\edce";
}
.nc-icon-mini.design_slice:before {
content: "\edcf";
}
.nc-icon-mini.design_spray:before {
content: "\edd0";
}
.nc-icon-mini.design_stamp:before {
content: "\edd1";
}
.nc-icon-mini.design_tablet:before {
content: "\edd2";
}
.nc-icon-mini.design_text:before {
content: "\edd3";
}
.nc-icon-mini.design_todo:before {
content: "\edd4";
}
.nc-icon-mini.design_usb:before {
content: "\edd5";
}
.nc-icon-mini.design_vector:before {
content: "\edd6";
}
.nc-icon-mini.design_wand:before {
content: "\edd7";
}
.nc-icon-mini.design_webpage:before {
content: "\edd8";
}
.nc-icon-mini.design_window-code:before {
content: "\edd9";
}
.nc-icon-mini.design_window-paragraph:before {
content: "\edda";
}
.nc-icon-mini.design_window-responsive:before {
content: "\eddb";
}
.nc-icon-mini.clothes_baby:before {
content: "\eddc";
}
.nc-icon-mini.clothes_backpack:before {
content: "\eddd";
}
.nc-icon-mini.clothes_bag-21:before {
content: "\edde";
}
.nc-icon-mini.clothes_bag-22:before {
content: "\eddf";
}
.nc-icon-mini.clothes_belt:before {
content: "\ede0";
}
.nc-icon-mini.clothes_boot-woman:before {
content: "\ede1";
}
.nc-icon-mini.clothes_boot:before {
content: "\ede2";
}
.nc-icon-mini.clothes_bra:before {
content: "\ede3";
}
.nc-icon-mini.clothes_button:before {
content: "\ede4";
}
.nc-icon-mini.clothes_cap:before {
content: "\ede5";
}
.nc-icon-mini.clothes_corset:before {
content: "\ede6";
}
.nc-icon-mini.clothes_dress-woman:before {
content: "\ede7";
}
.nc-icon-mini.clothes_flip:before {
content: "\ede8";
}
.nc-icon-mini.clothes_glasses:before {
content: "\ede9";
}
.nc-icon-mini.clothes_hat-top:before {
content: "\edea";
}
.nc-icon-mini.clothes_hat:before {
content: "\edeb";
}
.nc-icon-mini.clothes_iron:before {
content: "\edec";
}
.nc-icon-mini.clothes_jeans-41:before {
content: "\eded";
}
.nc-icon-mini.clothes_jeans-pocket:before {
content: "\edee";
}
.nc-icon-mini.clothes_kitchen:before {
content: "\edef";
}
.nc-icon-mini.clothes_long-sleeve:before {
content: "\edf0";
}
.nc-icon-mini.clothes_makeup:before {
content: "\edf1";
}
.nc-icon-mini.clothes_ring:before {
content: "\edf2";
}
.nc-icon-mini.clothes_scarf:before {
content: "\edf3";
}
.nc-icon-mini.clothes_shirt-buttons:before {
content: "\edf4";
}
.nc-icon-mini.clothes_shirt-neck:before {
content: "\edf5";
}
.nc-icon-mini.clothes_shirt:before {
content: "\edf6";
}
.nc-icon-mini.clothes_shoe-man:before {
content: "\edf7";
}
.nc-icon-mini.clothes_shoe-sport:before {
content: "\edf8";
}
.nc-icon-mini.clothes_shoe-woman:before {
content: "\edf9";
}
.nc-icon-mini.clothes_skirt:before {
content: "\edfa";
}
.nc-icon-mini.clothes_slacks-12:before {
content: "\edfb";
}
.nc-icon-mini.clothes_sock:before {
content: "\edfc";
}
.nc-icon-mini.clothes_tie-bow:before {
content: "\edfd";
}
.nc-icon-mini.clothes_tshirt-53:before {
content: "\edfe";
}
.nc-icon-mini.clothes_tshirt-54:before {
content: "\edff";
}
.nc-icon-mini.clothes_underwear-man:before {
content: "\ee00";
}
.nc-icon-mini.clothes_underwear:before {
content: "\ee01";
}
.nc-icon-mini.clothes_vest:before {
content: "\ee02";
}
.nc-icon-mini.clothes_wash:before {
content: "\ee03";
}
.nc-icon-mini.business_agenda:before {
content: "\ee04";
}
.nc-icon-mini.business_atm:before {
content: "\ee05";
}
.nc-icon-mini.business_award-48:before {
content: "\ee06";
}
.nc-icon-mini.business_award-49:before {
content: "\ee07";
}
.nc-icon-mini.business_award-74:before {
content: "\ee08";
}
.nc-icon-mini.business_badge:before {
content: "\ee09";
}
.nc-icon-mini.business_bank:before {
content: "\ee0a";
}
.nc-icon-mini.business_board-27:before {
content: "\ee0b";
}
.nc-icon-mini.business_board-28:before {
content: "\ee0c";
}
.nc-icon-mini.business_books:before {
content: "\ee0d";
}
.nc-icon-mini.business_briefcase-24:before {
content: "\ee0e";
}
.nc-icon-mini.business_briefcase-25:before {
content: "\ee0f";
}
.nc-icon-mini.business_briefcase-26:before {
content: "\ee10";
}
.nc-icon-mini.business_building:before {
content: "\ee11";
}
.nc-icon-mini.business_bulb-61:before {
content: "\ee12";
}
.nc-icon-mini.business_bulb-62:before {
content: "\ee13";
}
.nc-icon-mini.business_bulb-63:before {
content: "\ee14";
}
.nc-icon-mini.business_businessman-03:before {
content: "\ee15";
}
.nc-icon-mini.business_businessman-04:before {
content: "\ee16";
}
.nc-icon-mini.business_calculator:before {
content: "\ee17";
}
.nc-icon-mini.business_chair:before {
content: "\ee18";
}
.nc-icon-mini.business_chart-bar-32:before {
content: "\ee19";
}
.nc-icon-mini.business_chart-growth:before {
content: "\ee1a";
}
.nc-icon-mini.business_chart-pie-35:before {
content: "\ee1b";
}
.nc-icon-mini.business_chart-pie-36:before {
content: "\ee1c";
}
.nc-icon-mini.business_chart:before {
content: "\ee1d";
}
.nc-icon-mini.business_cheque:before {
content: "\ee1e";
}
.nc-icon-mini.business_coins:before {
content: "\ee1f";
}
.nc-icon-mini.business_connect:before {
content: "\ee20";
}
.nc-icon-mini.business_contacts:before {
content: "\ee21";
}
.nc-icon-mini.business_currency-dollar:before {
content: "\ee22";
}
.nc-icon-mini.business_currency-euro:before {
content: "\ee23";
}
.nc-icon-mini.business_currency-pound:before {
content: "\ee24";
}
.nc-icon-mini.business_currency-yen:before {
content: "\ee25";
}
.nc-icon-mini.business_factory:before {
content: "\ee26";
}
.nc-icon-mini.business_globe:before {
content: "\ee27";
}
.nc-icon-mini.business_goal-64:before {
content: "\ee28";
}
.nc-icon-mini.business_goal-65:before {
content: "\ee29";
}
.nc-icon-mini.business_hammer:before {
content: "\ee2a";
}
.nc-icon-mini.business_handout:before {
content: "\ee2b";
}
.nc-icon-mini.business_hat:before {
content: "\ee2c";
}
.nc-icon-mini.business_hierarchy-53:before {
content: "\ee2d";
}
.nc-icon-mini.business_math:before {
content: "\ee2e";
}
.nc-icon-mini.business_money-11:before {
content: "\ee2f";
}
.nc-icon-mini.business_money-12:before {
content: "\ee30";
}
.nc-icon-mini.business_money-13:before {
content: "\ee31";
}
.nc-icon-mini.business_money-bag:before {
content: "\ee32";
}
.nc-icon-mini.business_net:before {
content: "\ee33";
}
.nc-icon-mini.business_notes:before {
content: "\ee34";
}
.nc-icon-mini.business_percentage-39:before {
content: "\ee35";
}
.nc-icon-mini.business_pin:before {
content: "\ee36";
}
.nc-icon-mini.business_plug:before {
content: "\ee37";
}
.nc-icon-mini.business_progress:before {
content: "\ee38";
}
.nc-icon-mini.business_safe:before {
content: "\ee39";
}
.nc-icon-mini.business_sign:before {
content: "\ee3a";
}
.nc-icon-mini.business_signature:before {
content: "\ee3b";
}
.nc-icon-mini.business_stock:before {
content: "\ee3c";
}
.nc-icon-mini.business_strategy:before {
content: "\ee3d";
}
.nc-icon-mini.business_tie-01:before {
content: "\ee3e";
}
.nc-icon-mini.business_tie-02:before {
content: "\ee3f";
}
.nc-icon-mini.business_wallet-43:before {
content: "\ee40";
}
.nc-icon-mini.business_wallet-44:before {
content: "\ee41";
}
.nc-icon-mini.nature_bee:before {
content: "\ee42";
}
.nc-icon-mini.nature_butterfly:before {
content: "\ee43";
}
.nc-icon-mini.nature_chicken:before {
content: "\ee44";
}
.nc-icon-mini.nature_clover:before {
content: "\ee45";
}
.nc-icon-mini.nature_collar:before {
content: "\ee46";
}
.nc-icon-mini.nature_cow:before {
content: "\ee47";
}
.nc-icon-mini.nature_dog-house:before {
content: "\ee48";
}
.nc-icon-mini.nature_dog:before {
content: "\ee49";
}
.nc-icon-mini.nature_flower-05:before {
content: "\ee4a";
}
.nc-icon-mini.nature_flower-06:before {
content: "\ee4b";
}
.nc-icon-mini.nature_flower-07:before {
content: "\ee4c";
}
.nc-icon-mini.nature_food-dog:before {
content: "\ee4d";
}
.nc-icon-mini.nature_food:before {
content: "\ee4e";
}
.nc-icon-mini.nature_mountain:before {
content: "\ee4f";
}
.nc-icon-mini.nature_mushroom:before {
content: "\ee50";
}
.nc-icon-mini.nature_panda:before {
content: "\ee51";
}
.nc-icon-mini.nature_paw:before {
content: "\ee52";
}
.nc-icon-mini.nature_pig:before {
content: "\ee53";
}
.nc-icon-mini.nature_plant-ground:before {
content: "\ee54";
}
.nc-icon-mini.nature_plant-vase:before {
content: "\ee55";
}
.nc-icon-mini.nature_rat:before {
content: "\ee56";
}
.nc-icon-mini.nature_tree-01:before {
content: "\ee57";
}
.nc-icon-mini.nature_tree-02:before {
content: "\ee58";
}
.nc-icon-mini.nature_tree-03:before {
content: "\ee59";
}
.nc-icon-mini.nature_turtle:before {
content: "\ee5a";
}
.nc-icon-mini.nature_wood:before {
content: "\ee5b";
}
================================================
FILE: lib/shared/styles/nucleo/mini/less/mixins.less
================================================
.nc-rotate(@degrees, @rotation) {
filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=@rotation);
-webkit-transform: rotate(@degrees);
-moz-transform: rotate(@degrees);
-ms-transform: rotate(@degrees);
-o-transform: rotate(@degrees);
transform: rotate(@degrees);
}
.nc-flip(@horiz, @vert, @rotation) {
filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=@rotation);
-webkit-transform: scale(@horiz, @vert);
-moz-transform: scale(@horiz, @vert);
-ms-transform: scale(@horiz, @vert);
-o-transform: scale(@horiz, @vert);
transform: scale(@horiz, @vert);
}
================================================
FILE: lib/shared/styles/nucleo/mini/less/nucleo-mini.less
================================================
/* --------------------------------
Nucleo Mini Web Font - nucleoapp.com/
License - nucleoapp.com/license/
Created using IcoMoon - icomoon.io
-------------------------------- */
@import "./variables.less";
@font-face {
font-family: 'Nucleo Mini';
src: url('/fonts/nucleo-mini.eot');
src: url('/fonts/nucleo-mini.eot') format('embedded-opentype'),
url('/fonts/nucleo-mini.woff2') format('woff2'),
url('/fonts/nucleo-mini.woff') format('woff'),
url('/fonts/nucleo-mini.ttf') format('truetype'),
url('/fonts/nucleo-mini.svg') format('svg');
font-weight: normal;
font-style: normal;
}
:global {
/*------------------------
base class definition
-------------------------*/
.nc-icon-mini {
display: inline-block;
font: normal normal normal @nc-font-size-base/1 'Nucleo Mini';
font-size: inherit;
speak: none;
text-transform: none;
/* Better Font Rendering */
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
@import "./icons.less";
}
================================================
FILE: lib/shared/styles/nucleo/mini/less/variables.less
================================================
@nc-font-path: "/fonts";
@nc-font-size-base: 14px;
@nc-css-prefix: nc-icon;
@nc-background-color: #eee;
@nc-li-width: (30em / 14);
@nc-padding-width: (1em/3);
================================================
FILE: lib/shared/styles/nucleo/outline/less/icons.less
================================================
/*------------------------
font icons
-------------------------*/
.nc-icon-outline.envir_bulb-saver:before {
content: "\ecc4";
}
.nc-icon-outline.envir_bulb:before {
content: "\ecc5";
}
.nc-icon-outline.envir_car:before {
content: "\ecc6";
}
.nc-icon-outline.envir_fuel-electric:before {
content: "\ecc7";
}
.nc-icon-outline.envir_fuel:before {
content: "\ecc8";
}
.nc-icon-outline.envir_home:before {
content: "\ecc9";
}
.nc-icon-outline.envir_level:before {
content: "\ecca";
}
.nc-icon-outline.envir_panel:before {
content: "\eccb";
}
.nc-icon-outline.envir_radiation:before {
content: "\eccc";
}
.nc-icon-outline.envir_recycling:before {
content: "\eccd";
}
.nc-icon-outline.envir_save-planet:before {
content: "\ecce";
}
.nc-icon-outline.envir_waste-danger:before {
content: "\eccf";
}
.nc-icon-outline.envir_waste-recycling:before {
content: "\ecd0";
}
.nc-icon-outline.envir_waste:before {
content: "\ecd1";
}
.nc-icon-outline.envir_water-hand:before {
content: "\ecd2";
}
.nc-icon-outline.envir_water-sink:before {
content: "\ecd3";
}
.nc-icon-outline.envir_water:before {
content: "\ecd4";
}
.nc-icon-outline.envir_wind:before {
content: "\ecd5";
}
.nc-icon-outline.text_align-center:before {
content: "\ecaa";
}
.nc-icon-outline.text_align-justify:before {
content: "\ecab";
}
.nc-icon-outline.text_align-left:before {
content: "\ecac";
}
.nc-icon-outline.text_align-right:before {
content: "\ecad";
}
.nc-icon-outline.text_background:before {
content: "\ecae";
}
.nc-icon-outline.text_bold:before {
content: "\ecaf";
}
.nc-icon-outline.text_capitalize:before {
content: "\ecb0";
}
.nc-icon-outline.text_caps-all:before {
content: "\ecb1";
}
.nc-icon-outline.text_caps-small:before {
content: "\ecb2";
}
.nc-icon-outline.text_color:before {
content: "\ecb3";
}
.nc-icon-outline.text_edit:before {
content: "\ecb4";
}
.nc-icon-outline.text_italic:before {
content: "\ecb5";
}
.nc-icon-outline.text_line-height:before {
content: "\ecb6";
}
.nc-icon-outline.text_list-bullet:before {
content: "\ecb7";
}
.nc-icon-outline.text_list-numbers:before {
content: "\ecb8";
}
.nc-icon-outline.text_margin-left:before {
content: "\ecb9";
}
.nc-icon-outline.text_margin-right:before {
content: "\ecba";
}
.nc-icon-outline.text_quote:before {
content: "\ecbb";
}
.nc-icon-outline.text_scale-horizontal:before {
content: "\ecbc";
}
.nc-icon-outline.text_scale-vertical:before {
content: "\ecbd";
}
.nc-icon-outline.text_size:before {
content: "\ecbe";
}
.nc-icon-outline.text_strikethrough:before {
content: "\ecbf";
}
.nc-icon-outline.text_subscript:before {
content: "\ecc0";
}
.nc-icon-outline.text_superscript:before {
content: "\ecc1";
}
.nc-icon-outline.text_tracking:before {
content: "\ecc2";
}
.nc-icon-outline.text_underline:before {
content: "\ecc3";
}
.nc-icon-outline.gestures_2x-drag-down:before {
content: "\ec09";
}
.nc-icon-outline.gestures_2x-drag-up:before {
content: "\ec0a";
}
.nc-icon-outline.gestures_2x-swipe-down:before {
content: "\ec0b";
}
.nc-icon-outline.gestures_2x-swipe-left:before {
content: "\ec0c";
}
.nc-icon-outline.gestures_2x-swipe-right:before {
content: "\ec0d";
}
.nc-icon-outline.gestures_2x-swipe-up:before {
content: "\ec0e";
}
.nc-icon-outline.gestures_2x-tap:before {
content: "\ec0f";
}
.nc-icon-outline.gestures_3x-swipe-left:before {
content: "\ec10";
}
.nc-icon-outline.gestures_3x-swipe-right:before {
content: "\ec11";
}
.nc-icon-outline.gestures_3x-swipe-up:before {
content: "\ec12";
}
.nc-icon-outline.gestures_3x-tap:before {
content: "\ec13";
}
.nc-icon-outline.gestures_4x-swipe-left:before {
content: "\ec14";
}
.nc-icon-outline.gestures_4x-swipe-right:before {
content: "\ec15";
}
.nc-icon-outline.gestures_4x-swipe-up:before {
content: "\ec16";
}
.nc-icon-outline.gestures_active-38:before {
content: "\ec17";
}
.nc-icon-outline.gestures_active-40:before {
content: "\ec18";
}
.nc-icon-outline.gestures_camera:before {
content: "\ec19";
}
.nc-icon-outline.gestures_double-tap:before {
content: "\ec1a";
}
.nc-icon-outline.gestures_drag-21:before {
content: "\ec1b";
}
.nc-icon-outline.gestures_drag-31:before {
content: "\ec1c";
}
.nc-icon-outline.gestures_drag-down:before {
content: "\ec1d";
}
.nc-icon-outline.gestures_drag-left:before {
content: "\ec1e";
}
.nc-icon-outline.gestures_drag-right:before {
content: "\ec1f";
}
.nc-icon-outline.gestures_drag-up:before {
content: "\ec20";
}
.nc-icon-outline.gestures_flick-down:before {
content: "\ec21";
}
.nc-icon-outline.gestures_flick-left:before {
content: "\ec22";
}
.nc-icon-outline.gestures_flick-right:before {
content: "\ec23";
}
.nc-icon-outline.gestures_flick-up:before {
content: "\ec24";
}
.nc-icon-outline.gestures_grab:before {
content: "\ec25";
}
.nc-icon-outline.gestures_hold:before {
content: "\ec26";
}
.nc-icon-outline.gestures_pin:before {
content: "\ec27";
}
.nc-icon-outline.gestures_pinch:before {
content: "\ec28";
}
.nc-icon-outline.gestures_rotate-22:before {
content: "\ec29";
}
.nc-icon-outline.gestures_rotate-23:before {
content: "\ec2a";
}
.nc-icon-outline.gestures_scan:before {
content: "\ec2b";
}
.nc-icon-outline.gestures_scroll-horitontal:before {
content: "\ec2c";
}
.nc-icon-outline.gestures_scroll-vertical:before {
content: "\ec2d";
}
.nc-icon-outline.gestures_stretch:before {
content: "\ec2e";
}
.nc-icon-outline.gestures_swipe-bottom:before {
content: "\ec2f";
}
.nc-icon-outline.gestures_swipe-left:before {
content: "\ec30";
}
.nc-icon-outline.gestures_swipe-right:before {
content: "\ec31";
}
.nc-icon-outline.gestures_swipe-up:before {
content: "\ec32";
}
.nc-icon-outline.gestures_tap-01:before {
content: "\ec33";
}
.nc-icon-outline.gestures_tap-02:before {
content: "\ec34";
}
.nc-icon-outline.sport_badminton:before {
content: "\ec35";
}
.nc-icon-outline.sport_baseball-ball:before {
content: "\ec36";
}
.nc-icon-outline.sport_baseball-bat:before {
content: "\ec37";
}
.nc-icon-outline.sport_baseball:before {
content: "\ec38";
}
.nc-icon-outline.sport_basketball-12:before {
content: "\ec39";
}
.nc-icon-outline.sport_basketball-13:before {
content: "\ec3a";
}
.nc-icon-outline.sport_boxing:before {
content: "\ec3b";
}
.nc-icon-outline.sport_cardio:before {
content: "\ec3c";
}
.nc-icon-outline.sport_cricket:before {
content: "\ec3d";
}
.nc-icon-outline.sport_crown:before {
content: "\ec3e";
}
.nc-icon-outline.sport_dart:before {
content: "\ec3f";
}
.nc-icon-outline.sport_dumbbells:before {
content: "\ec40";
}
.nc-icon-outline.sport_energy-drink:before {
content: "\ec41";
}
.nc-icon-outline.sport_energy-supplement:before {
content: "\ec42";
}
.nc-icon-outline.sport_fencing:before {
content: "\ec43";
}
.nc-icon-outline.sport_fishing:before {
content: "\ec44";
}
.nc-icon-outline.sport_flag-finish:before {
content: "\ec45";
}
.nc-icon-outline.sport_football-headguard:before {
content: "\ec46";
}
.nc-icon-outline.sport_golf:before {
content: "\ec47";
}
.nc-icon-outline.sport_helmet:before {
content: "\ec48";
}
.nc-icon-outline.sport_hockey:before {
content: "\ec49";
}
.nc-icon-outline.sport_kettlebell:before {
content: "\ec4a";
}
.nc-icon-outline.sport_ping-pong:before {
content: "\ec4b";
}
.nc-icon-outline.sport_podium-trophy:before {
content: "\ec4c";
}
.nc-icon-outline.sport_podium:before {
content: "\ec4d";
}
.nc-icon-outline.sport_rope:before {
content: "\ec4e";
}
.nc-icon-outline.sport_rugby:before {
content: "\ec4f";
}
.nc-icon-outline.sport_shaker:before {
content: "\ec50";
}
.nc-icon-outline.sport_shoe-run:before {
content: "\ec51";
}
.nc-icon-outline.sport_skateboard:before {
content: "\ec52";
}
.nc-icon-outline.sport_snowboard:before {
content: "\ec53";
}
.nc-icon-outline.sport_soccer-field:before {
content: "\ec54";
}
.nc-icon-outline.sport_steering-wheel:before {
content: "\ec55";
}
.nc-icon-outline.sport_supplement:before {
content: "\ec56";
}
.nc-icon-outline.sport_surf:before {
content: "\ec57";
}
.nc-icon-outline.sport_tactic:before {
content: "\ec58";
}
.nc-icon-outline.sport_tennis-ball:before {
content: "\ec59";
}
.nc-icon-outline.sport_tennis:before {
content: "\ec5a";
}
.nc-icon-outline.sport_trophy:before {
content: "\ec5b";
}
.nc-icon-outline.sport_user-balance:before {
content: "\ec5c";
}
.nc-icon-outline.sport_user-climb:before {
content: "\ec5d";
}
.nc-icon-outline.sport_user-meditation:before {
content: "\ec5e";
}
.nc-icon-outline.sport_user-run:before {
content: "\ec5f";
}
.nc-icon-outline.sport_user-snowboard:before {
content: "\ec60";
}
.nc-icon-outline.sport_user-swim:before {
content: "\ec61";
}
.nc-icon-outline.sport_volleyball:before {
content: "\ec62";
}
.nc-icon-outline.sport_whistle:before {
content: "\ec63";
}
.nc-icon-outline.holidays_bat:before {
content: "\ec64";
}
.nc-icon-outline.holidays_biscuit:before {
content: "\ec65";
}
.nc-icon-outline.holidays_bones:before {
content: "\ec66";
}
.nc-icon-outline.holidays_boot:before {
content: "\ec67";
}
.nc-icon-outline.holidays_candy:before {
content: "\ec68";
}
.nc-icon-outline.holidays_cat:before {
content: "\ec69";
}
.nc-icon-outline.holidays_cauldron:before {
content: "\ec6a";
}
.nc-icon-outline.holidays_chimney:before {
content: "\ec6b";
}
.nc-icon-outline.holidays_cockade:before {
content: "\ec6c";
}
.nc-icon-outline.holidays_coffin:before {
content: "\ec6d";
}
.nc-icon-outline.holidays_dead-hand:before {
content: "\ec6e";
}
.nc-icon-outline.holidays_decoration:before {
content: "\ec6f";
}
.nc-icon-outline.holidays_deer:before {
content: "\ec70";
}
.nc-icon-outline.holidays_egg-38:before {
content: "\ec71";
}
.nc-icon-outline.holidays_egg-39:before {
content: "\ec72";
}
.nc-icon-outline.holidays_frankenstein:before {
content: "\ec73";
}
.nc-icon-outline.holidays_ghost:before {
content: "\ec74";
}
.nc-icon-outline.holidays_gift-exchange:before {
content: "\ec75";
}
.nc-icon-outline.holidays_gift:before {
content: "\ec76";
}
.nc-icon-outline.holidays_glove:before {
content: "\ec77";
}
.nc-icon-outline.holidays_grave:before {
content: "\ec78";
}
.nc-icon-outline.holidays_light:before {
content: "\ec79";
}
.nc-icon-outline.holidays_message:before {
content: "\ec7a";
}
.nc-icon-outline.holidays_mistletoe:before {
content: "\ec7b";
}
.nc-icon-outline.holidays_owl:before {
content: "\ec7c";
}
.nc-icon-outline.holidays_pumpkin:before {
content: "\ec7d";
}
.nc-icon-outline.holidays_rabbit:before {
content: "\ec7e";
}
.nc-icon-outline.holidays_santa-hat:before {
content: "\ec7f";
}
.nc-icon-outline.holidays_sickle:before {
content: "\ec80";
}
.nc-icon-outline.holidays_snow-ball:before {
content: "\ec81";
}
.nc-icon-outline.holidays_snowman-head:before {
content: "\ec82";
}
.nc-icon-outline.holidays_snowman:before {
content: "\ec83";
}
.nc-icon-outline.holidays_soak:before {
content: "\ec84";
}
.nc-icon-outline.holidays_spider:before {
content: "\ec85";
}
.nc-icon-outline.holidays_tree-ball:before {
content: "\ec86";
}
.nc-icon-outline.holidays_tree:before {
content: "\ec87";
}
.nc-icon-outline.holidays_vampire:before {
content: "\ec88";
}
.nc-icon-outline.holidays_witch-hat:before {
content: "\ec89";
}
.nc-icon-outline.holidays_wolf:before {
content: "\ec8a";
}
.nc-icon-outline.holidays_zombie:before {
content: "\ec8b";
}
.nc-icon-outline.nature_bear:before {
content: "\ec8c";
}
.nc-icon-outline.nature_bee:before {
content: "\ec8d";
}
.nc-icon-outline.nature_butterfly:before {
content: "\ec8e";
}
.nc-icon-outline.nature_chicken:before {
content: "\ec8f";
}
.nc-icon-outline.nature_clover:before {
content: "\ec90";
}
.nc-icon-outline.nature_collar:before {
content: "\ec91";
}
.nc-icon-outline.nature_cow:before {
content: "\ec92";
}
.nc-icon-outline.nature_dog-house:before {
content: "\ec93";
}
.nc-icon-outline.nature_dog:before {
content: "\ec94";
}
.nc-icon-outline.nature_flower-05:before {
content: "\ec95";
}
.nc-icon-outline.nature_flower-06:before {
content: "\ec96";
}
.nc-icon-outline.nature_flower-07:before {
content: "\ec97";
}
.nc-icon-outline.nature_food-dog:before {
content: "\ec98";
}
.nc-icon-outline.nature_food:before {
content: "\ec99";
}
.nc-icon-outline.nature_forest:before {
content: "\ec9a";
}
.nc-icon-outline.nature_mountain:before {
content: "\ec9b";
}
.nc-icon-outline.nature_mushroom:before {
content: "\ec9c";
}
.nc-icon-outline.nature_panda:before {
content: "\ec9d";
}
.nc-icon-outline.nature_paw:before {
content: "\ec9e";
}
.nc-icon-outline.nature_pig:before {
content: "\ec9f";
}
.nc-icon-outline.nature_plant-ground:before {
content: "\eca0";
}
.nc-icon-outline.nature_plant-vase:before {
content: "\eca1";
}
.nc-icon-outline.nature_rat:before {
content: "\eca2";
}
.nc-icon-outline.nature_sheep:before {
content: "\eca3";
}
.nc-icon-outline.nature_snake:before {
content: "\eca4";
}
.nc-icon-outline.nature_tree-01:before {
content: "\eca5";
}
.nc-icon-outline.nature_tree-02:before {
content: "\eca6";
}
.nc-icon-outline.nature_tree-03:before {
content: "\eca7";
}
.nc-icon-outline.nature_turtle:before {
content: "\eca8";
}
.nc-icon-outline.nature_wood:before {
content: "\eca9";
}
.nc-icon-outline.travel_axe:before {
content: "\eb28";
}
.nc-icon-outline.travel_backpack:before {
content: "\eb29";
}
.nc-icon-outline.travel_bag:before {
content: "\eb2a";
}
.nc-icon-outline.travel_barbecue:before {
content: "\eb2b";
}
.nc-icon-outline.travel_beach-umbrella:before {
content: "\eb2c";
}
.nc-icon-outline.travel_berlin:before {
content: "\eb2d";
}
.nc-icon-outline.travel_binocular:before {
content: "\eb2e";
}
.nc-icon-outline.travel_camper:before {
content: "\eb2f";
}
.nc-icon-outline.travel_camping:before {
content: "\eb30";
}
.nc-icon-outline.travel_castle:before {
content: "\eb31";
}
.nc-icon-outline.travel_china:before {
content: "\eb32";
}
.nc-icon-outline.travel_church:before {
content: "\eb33";
}
.nc-icon-outline.travel_drink:before {
content: "\eb34";
}
.nc-icon-outline.travel_explore:before {
content: "\eb35";
}
.nc-icon-outline.travel_fire:before {
content: "\eb36";
}
.nc-icon-outline.travel_hotel-bell:before {
content: "\eb37";
}
.nc-icon-outline.travel_hotel-symbol:before {
content: "\eb38";
}
.nc-icon-outline.travel_hotel:before {
content: "\eb39";
}
.nc-icon-outline.travel_hut:before {
content: "\eb3a";
}
.nc-icon-outline.travel_igloo:before {
content: "\eb3b";
}
.nc-icon-outline.travel_info:before {
content: "\eb3c";
}
.nc-icon-outline.travel_istanbul:before {
content: "\eb3d";
}
.nc-icon-outline.travel_jellyfish:before {
content: "\eb3e";
}
.nc-icon-outline.travel_lamp:before {
content: "\eb3f";
}
.nc-icon-outline.travel_lighthouse:before {
content: "\eb40";
}
.nc-icon-outline.travel_london:before {
content: "\eb41";
}
.nc-icon-outline.travel_luggage:before {
content: "\eb42";
}
.nc-icon-outline.travel_mosque:before {
content: "\eb43";
}
.nc-icon-outline.travel_ny:before {
content: "\eb44";
}
.nc-icon-outline.travel_octopus:before {
content: "\eb45";
}
.nc-icon-outline.travel_paris-tower:before {
content: "\eb46";
}
.nc-icon-outline.travel_passport:before {
content: "\eb47";
}
.nc-icon-outline.travel_pickaxe:before {
content: "\eb48";
}
.nc-icon-outline.travel_pool:before {
content: "\eb49";
}
.nc-icon-outline.travel_pyramid:before {
content: "\eb4a";
}
.nc-icon-outline.travel_rackets:before {
content: "\eb4b";
}
.nc-icon-outline.travel_rio:before {
content: "\eb4c";
}
.nc-icon-outline.travel_road-sign-left:before {
content: "\eb4d";
}
.nc-icon-outline.travel_road-sign-right:before {
content: "\eb4e";
}
.nc-icon-outline.travel_rome:before {
content: "\eb4f";
}
.nc-icon-outline.travel_rowing:before {
content: "\eb50";
}
.nc-icon-outline.travel_sea-mask:before {
content: "\eb51";
}
.nc-icon-outline.travel_sf-bridge:before {
content: "\eb52";
}
.nc-icon-outline.travel_shark:before {
content: "\eb53";
}
.nc-icon-outline.travel_spa:before {
content: "\eb54";
}
.nc-icon-outline.travel_sunglasses:before {
content: "\eb55";
}
.nc-icon-outline.travel_surf:before {
content: "\eb56";
}
.nc-icon-outline.travel_swimsuit:before {
content: "\eb57";
}
.nc-icon-outline.travel_swimwear:before {
content: "\eb58";
}
.nc-icon-outline.travel_swiss-knife:before {
content: "\eb59";
}
.nc-icon-outline.travel_temple-02:before {
content: "\eb5a";
}
.nc-icon-outline.travel_temple-25:before {
content: "\eb5b";
}
.nc-icon-outline.travel_trolley:before {
content: "\eb5c";
}
.nc-icon-outline.travel_white-house:before {
content: "\eb5d";
}
.nc-icon-outline.travel_world:before {
content: "\eb5e";
}
.nc-icon-outline.travel_worldmap:before {
content: "\eb5f";
}
.nc-icon-outline.food_alcohol:before {
content: "\eb60";
}
.nc-icon-outline.food_apple:before {
content: "\eb61";
}
.nc-icon-outline.food_baby:before {
content: "\eb62";
}
.nc-icon-outline.food_bacon:before {
content: "\eb63";
}
.nc-icon-outline.food_baguette:before {
content: "\eb64";
}
.nc-icon-outline.food_banana:before {
content: "\eb65";
}
.nc-icon-outline.food_barbecue-02:before {
content: "\eb66";
}
.nc-icon-outline.food_barbecue-15:before {
content: "\eb67";
}
.nc-icon-outline.food_barbecue-tools:before {
content: "\eb68";
}
.nc-icon-outline.food_beer-95:before {
content: "\eb69";
}
.nc-icon-outline.food_beer-96:before {
content: "\eb6a";
}
.nc-icon-outline.food_beverage:before {
content: "\eb6b";
}
.nc-icon-outline.food_bottle-wine:before {
content: "\eb6c";
}
.nc-icon-outline.food_bottle:before {
content: "\eb6d";
}
.nc-icon-outline.food_bowl:before {
content: "\eb6e";
}
.nc-icon-outline.food_bread:before {
content: "\eb6f";
}
.nc-icon-outline.food_broccoli:before {
content: "\eb70";
}
.nc-icon-outline.food_cake-13:before {
content: "\eb71";
}
.nc-icon-outline.food_cake-100:before {
content: "\eb72";
}
.nc-icon-outline.food_cake-slice:before {
content: "\eb73";
}
.nc-icon-outline.food_candle:before {
content: "\eb74";
}
.nc-icon-outline.food_candy:before {
content: "\eb75";
}
.nc-icon-outline.food_carrot:before {
content: "\eb76";
}
.nc-icon-outline.food_champagne:before {
content: "\eb77";
}
.nc-icon-outline.food_cheese-24:before {
content: "\eb78";
}
.nc-icon-outline.food_cheese-87:before {
content: "\eb79";
}
.nc-icon-outline.food_cheeseburger:before {
content: "\eb7a";
}
.nc-icon-outline.food_chef-hat:before {
content: "\eb7b";
}
.nc-icon-outline.food_cherry:before {
content: "\eb7c";
}
.nc-icon-outline.food_chicken:before {
content: "\eb7d";
}
.nc-icon-outline.food_chili:before {
content: "\eb7e";
}
.nc-icon-outline.food_chinese:before {
content: "\eb7f";
}
.nc-icon-outline.food_chips:before {
content: "\eb80";
}
.nc-icon-outline.food_chocolate:before {
content: "\eb81";
}
.nc-icon-outline.food_cocktail:before {
content: "\eb82";
}
.nc-icon-outline.food_coffe-long:before {
content: "\eb83";
}
.nc-icon-outline.food_coffee-long:before {
content: "\eb84";
}
.nc-icon-outline.food_coffee:before {
content: "\eb85";
}
.nc-icon-outline.food_cookies:before {
content: "\eb86";
}
.nc-icon-outline.food_course:before {
content: "\eb87";
}
.nc-icon-outline.food_crab:before {
content: "\eb88";
}
.nc-icon-outline.food_croissant:before {
content: "\eb89";
}
.nc-icon-outline.food_cutlery-75:before {
content: "\eb8a";
}
.nc-icon-outline.food_cutlery-76:before {
content: "\eb8b";
}
.nc-icon-outline.food_cutlery-77:before {
content: "\eb8c";
}
.nc-icon-outline.food_dishwasher:before {
content: "\eb8d";
}
.nc-icon-outline.food_donut:before {
content: "\eb8e";
}
.nc-icon-outline.food_drink:before {
content: "\eb8f";
}
.nc-icon-outline.food_egg:before {
content: "\eb90";
}
.nc-icon-outline.food_energy-drink:before {
content: "\eb91";
}
.nc-icon-outline.food_fish:before {
content: "\eb92";
}
.nc-icon-outline.food_fishbone:before {
content: "\eb93";
}
.nc-icon-outline.food_fridge:before {
content: "\eb94";
}
.nc-icon-outline.food_glass:before {
content: "\eb95";
}
.nc-icon-outline.food_grape:before {
content: "\eb96";
}
.nc-icon-outline.food_hob:before {
content: "\eb97";
}
.nc-icon-outline.food_hot-dog:before {
content: "\eb98";
}
.nc-icon-outline.food_ice-cream-22:before {
content: "\eb99";
}
.nc-icon-outline.food_ice-cream-72:before {
content: "\eb9a";
}
.nc-icon-outline.food_jam:before {
content: "\eb9b";
}
.nc-icon-outline.food_kettle:before {
content: "\eb9c";
}
.nc-icon-outline.food_kitchen-fan:before {
content: "\eb9d";
}
.nc-icon-outline.food_knife:before {
content: "\eb9e";
}
.nc-icon-outline.food_lemon-slice:before {
content: "\eb9f";
}
.nc-icon-outline.food_lighter:before {
content: "\eba0";
}
.nc-icon-outline.food_lobster:before {
content: "\eba1";
}
.nc-icon-outline.food_matches:before {
content: "\eba2";
}
.nc-icon-outline.food_measuring-cup:before {
content: "\eba3";
}
.nc-icon-outline.food_meat-spit:before {
content: "\eba4";
}
.nc-icon-outline.food_microwave:before {
content: "\eba5";
}
.nc-icon-outline.food_milk:before {
content: "\eba6";
}
.nc-icon-outline.food_moka:before {
content: "\eba7";
}
.nc-icon-outline.food_muffin:before {
content: "\eba8";
}
.nc-icon-outline.food_mug:before {
content: "\eba9";
}
.nc-icon-outline.food_oven:before {
content: "\ebaa";
}
.nc-icon-outline.food_pan:before {
content: "\ebab";
}
.nc-icon-outline.food_pizza-slice:before {
content: "\ebac";
}
.nc-icon-outline.food_pizza:before {
content: "\ebad";
}
.nc-icon-outline.food_plate:before {
content: "\ebae";
}
.nc-icon-outline.food_pot:before {
content: "\ebaf";
}
.nc-icon-outline.food_prosciutto:before {
content: "\ebb0";
}
.nc-icon-outline.food_recipe-book-46:before {
content: "\ebb1";
}
.nc-icon-outline.food_recipe-book-47:before {
content: "\ebb2";
}
.nc-icon-outline.food_rolling-pin:before {
content: "\ebb3";
}
.nc-icon-outline.food_salt:before {
content: "\ebb4";
}
.nc-icon-outline.food_sausage:before {
content: "\ebb5";
}
.nc-icon-outline.food_scale:before {
content: "\ebb6";
}
.nc-icon-outline.food_scotch:before {
content: "\ebb7";
}
.nc-icon-outline.food_shrimp:before {
content: "\ebb8";
}
.nc-icon-outline.food_steak:before {
content: "\ebb9";
}
.nc-icon-outline.food_store:before {
content: "\ebba";
}
.nc-icon-outline.food_strawberry:before {
content: "\ebbb";
}
.nc-icon-outline.food_sushi:before {
content: "\ebbc";
}
.nc-icon-outline.food_tacos:before {
content: "\ebbd";
}
.nc-icon-outline.food_tea:before {
content: "\ebbe";
}
.nc-icon-outline.food_temperature:before {
content: "\ebbf";
}
.nc-icon-outline.food_vest-07:before {
content: "\ebc0";
}
.nc-icon-outline.food_vest-31:before {
content: "\ebc1";
}
.nc-icon-outline.food_watermelon:before {
content: "\ebc2";
}
.nc-icon-outline.food_whisk:before {
content: "\ebc3";
}
.nc-icon-outline.emoticons_alien:before {
content: "\ebc4";
}
.nc-icon-outline.emoticons_angry-10:before {
content: "\ebc5";
}
.nc-icon-outline.emoticons_angry-44:before {
content: "\ebc6";
}
.nc-icon-outline.emoticons_big-eyes:before {
content: "\ebc7";
}
.nc-icon-outline.emoticons_big-smile:before {
content: "\ebc8";
}
.nc-icon-outline.emoticons_bigmouth:before {
content: "\ebc9";
}
.nc-icon-outline.emoticons_bleah:before {
content: "\ebca";
}
.nc-icon-outline.emoticons_blind:before {
content: "\ebcb";
}
.nc-icon-outline.emoticons_bomb:before {
content: "\ebcc";
}
.nc-icon-outline.emoticons_bored:before {
content: "\ebcd";
}
.nc-icon-outline.emoticons_cake:before {
content: "\ebce";
}
.nc-icon-outline.emoticons_cry-15:before {
content: "\ebcf";
}
.nc-icon-outline.emoticons_cry-57:before {
content: "\ebd0";
}
.nc-icon-outline.emoticons_cute:before {
content: "\ebd1";
}
.nc-icon-outline.emoticons_devil:before {
content: "\ebd2";
}
.nc-icon-outline.emoticons_disgusted:before {
content: "\ebd3";
}
.nc-icon-outline.emoticons_fist:before {
content: "\ebd4";
}
.nc-icon-outline.emoticons_ghost:before {
content: "\ebd5";
}
.nc-icon-outline.emoticons_hannibal:before {
content: "\ebd6";
}
.nc-icon-outline.emoticons_happy-sun:before {
content: "\ebd7";
}
.nc-icon-outline.emoticons_kid:before {
content: "\ebd8";
}
.nc-icon-outline.emoticons_kiss:before {
content: "\ebd9";
}
.nc-icon-outline.emoticons_laugh-17:before {
content: "\ebda";
}
.nc-icon-outline.emoticons_laugh-35:before {
content: "\ebdb";
}
.nc-icon-outline.emoticons_like-no:before {
content: "\ebdc";
}
.nc-icon-outline.emoticons_like:before {
content: "\ebdd";
}
.nc-icon-outline.emoticons_mad-12:before {
content: "\ebde";
}
.nc-icon-outline.emoticons_mad-58:before {
content: "\ebdf";
}
.nc-icon-outline.emoticons_malicious:before {
content: "\ebe0";
}
.nc-icon-outline.emoticons_manga-62:before {
content: "\ebe1";
}
.nc-icon-outline.emoticons_manga-63:before {
content: "\ebe2";
}
.nc-icon-outline.emoticons_monster:before {
content: "\ebe3";
}
.nc-icon-outline.emoticons_nerd-22:before {
content: "\ebe4";
}
.nc-icon-outline.emoticons_nerd-23:before {
content: "\ebe5";
}
.nc-icon-outline.emoticons_ninja:before {
content: "\ebe6";
}
.nc-icon-outline.emoticons_no-words:before {
content: "\ebe7";
}
.nc-icon-outline.emoticons_parrot:before {
content: "\ebe8";
}
.nc-icon-outline.emoticons_penguin:before {
content: "\ebe9";
}
.nc-icon-outline.emoticons_pirate:before {
content: "\ebea";
}
.nc-icon-outline.emoticons_poop:before {
content: "\ebeb";
}
.nc-icon-outline.emoticons_puzzled:before {
content: "\ebec";
}
.nc-icon-outline.emoticons_quite-happy:before {
content: "\ebed";
}
.nc-icon-outline.emoticons_robot:before {
content: "\ebee";
}
.nc-icon-outline.emoticons_rock:before {
content: "\ebef";
}
.nc-icon-outline.emoticons_sad:before {
content: "\ebf0";
}
.nc-icon-outline.emoticons_satisfied:before {
content: "\ebf1";
}
.nc-icon-outline.emoticons_shark:before {
content: "\ebf2";
}
.nc-icon-outline.emoticons_shy:before {
content: "\ebf3";
}
.nc-icon-outline.emoticons_sick:before {
content: "\ebf4";
}
.nc-icon-outline.emoticons_silly:before {
content: "\ebf5";
}
.nc-icon-outline.emoticons_skull:before {
content: "\ebf6";
}
.nc-icon-outline.emoticons_sleep:before {
content: "\ebf7";
}
.nc-icon-outline.emoticons_sloth:before {
content: "\ebf8";
}
.nc-icon-outline.emoticons_smart:before {
content: "\ebf9";
}
.nc-icon-outline.emoticons_smile:before {
content: "\ebfa";
}
.nc-icon-outline.emoticons_soldier:before {
content: "\ebfb";
}
.nc-icon-outline.emoticons_speechless:before {
content: "\ebfc";
}
.nc-icon-outline.emoticons_spiteful:before {
content: "\ebfd";
}
.nc-icon-outline.emoticons_sunglasses-48:before {
content: "\ebfe";
}
.nc-icon-outline.emoticons_sunglasses-49:before {
content: "\ebff";
}
.nc-icon-outline.emoticons_surprise:before {
content: "\ec00";
}
.nc-icon-outline.emoticons_upset-13:before {
content: "\ec01";
}
.nc-icon-outline.emoticons_upset-14:before {
content: "\ec02";
}
.nc-icon-outline.emoticons_virus:before {
content: "\ec03";
}
.nc-icon-outline.emoticons_what:before {
content: "\ec04";
}
.nc-icon-outline.emoticons_whiskers:before {
content: "\ec05";
}
.nc-icon-outline.emoticons_wink-06:before {
content: "\ec06";
}
.nc-icon-outline.emoticons_wink-11:before {
content: "\ec07";
}
.nc-icon-outline.emoticons_wink-69:before {
content: "\ec08";
}
.nc-icon-outline.weather_celsius:before {
content: "\e600";
}
.nc-icon-outline.weather_cloud-13:before {
content: "\e601";
}
.nc-icon-outline.weather_cloud-14:before {
content: "\e602";
}
.nc-icon-outline.weather_cloud-drop:before {
content: "\e603";
}
.nc-icon-outline.weather_cloud-fog-31:before {
content: "\e604";
}
.nc-icon-outline.weather_cloud-fog-32:before {
content: "\e605";
}
.nc-icon-outline.weather_cloud-hail:before {
content: "\e606";
}
.nc-icon-outline.weather_cloud-light:before {
content: "\e607";
}
.nc-icon-outline.weather_cloud-moon:before {
content: "\e608";
}
.nc-icon-outline.weather_cloud-rain:before {
content: "\e609";
}
.nc-icon-outline.weather_cloud-rainbow:before {
content: "\e60a";
}
.nc-icon-outline.weather_cloud-snow-34:before {
content: "\e60b";
}
.nc-icon-outline.weather_cloud-snow-42:before {
content: "\e60c";
}
.nc-icon-outline.weather_cloud-sun-17:before {
content: "\e60d";
}
.nc-icon-outline.weather_cloud-sun-19:before {
content: "\e60e";
}
.nc-icon-outline.weather_compass:before {
content: "\e60f";
}
.nc-icon-outline.weather_drop-12:before {
content: "\e610";
}
.nc-icon-outline.weather_drop-15:before {
content: "\e611";
}
.nc-icon-outline.weather_drops:before {
content: "\e612";
}
.nc-icon-outline.weather_eclipse:before {
content: "\e613";
}
.nc-icon-outline.weather_fahrenheit:before {
content: "\e614";
}
.nc-icon-outline.weather_fog:before {
content: "\e615";
}
.nc-icon-outline.weather_forecast:before {
content: "\e616";
}
.nc-icon-outline.weather_hurricane-44:before {
content: "\e617";
}
.nc-icon-outline.weather_hurricane-45:before {
content: "\e618";
}
.nc-icon-outline.weather_moon-cloud-drop:before {
content: "\e619";
}
.nc-icon-outline.weather_moon-cloud-fog:before {
content: "\e61a";
}
.nc-icon-outline.weather_moon-cloud-hail:before {
content: "\e61b";
}
.nc-icon-outline.weather_moon-cloud-light:before {
content: "\e61c";
}
.nc-icon-outline.weather_moon-cloud-rain:before {
content: "\e61d";
}
.nc-icon-outline.weather_moon-cloud-snow-61:before {
content: "\e61e";
}
.nc-icon-outline.weather_moon-cloud-snow-62:before {
content: "\e61f";
}
.nc-icon-outline.weather_moon-fog:before {
content: "\e620";
}
.nc-icon-outline.weather_moon-full:before {
content: "\e621";
}
.nc-icon-outline.weather_moon-stars:before {
content: "\e622";
}
.nc-icon-outline.weather_moon:before {
content: "\e623";
}
.nc-icon-outline.weather_rain-hail:before {
content: "\e624";
}
.nc-icon-outline.weather_rain:before {
content: "\e625";
}
.nc-icon-outline.weather_rainbow:before {
content: "\e626";
}
.nc-icon-outline.weather_snow:before {
content: "\e627";
}
.nc-icon-outline.weather_sun-cloud-drop:before {
content: "\e628";
}
.nc-icon-outline.weather_sun-cloud-fog:before {
content: "\e629";
}
.nc-icon-outline.weather_sun-cloud-hail:before {
content: "\e62a";
}
.nc-icon-outline.weather_sun-cloud-light:before {
content: "\e62b";
}
.nc-icon-outline.weather_sun-cloud-rain:before {
content: "\e62c";
}
.nc-icon-outline.weather_sun-cloud-snow-54:before {
content: "\e62d";
}
.nc-icon-outline.weather_sun-cloud-snow-55:before {
content: "\e62e";
}
.nc-icon-outline.weather_sun-cloud:before {
content: "\e62f";
}
.nc-icon-outline.weather_sun-fog-29:before {
content: "\e630";
}
.nc-icon-outline.weather_sun-fog-30:before {
content: "\e631";
}
.nc-icon-outline.weather_sun-fog-43:before {
content: "\e632";
}
.nc-icon-outline.weather_sun:before {
content: "\e633";
}
.nc-icon-outline.weather_wind:before {
content: "\e634";
}
.nc-icon-outline.users_add-27:before {
content: "\e635";
}
.nc-icon-outline.users_add-29:before {
content: "\e636";
}
.nc-icon-outline.users_badge-13:before {
content: "\e637";
}
.nc-icon-outline.users_badge-14:before {
content: "\e638";
}
.nc-icon-outline.users_badge-15:before {
content: "\e639";
}
.nc-icon-outline.users_circle-08:before {
content: "\e63a";
}
.nc-icon-outline.users_circle-09:before {
content: "\e63b";
}
.nc-icon-outline.users_circle-10:before {
content: "\e63c";
}
.nc-icon-outline.users_contacts:before {
content: "\e63d";
}
.nc-icon-outline.users_delete-28:before {
content: "\e63e";
}
.nc-icon-outline.users_delete-30:before {
content: "\e63f";
}
.nc-icon-outline.users_man-20:before {
content: "\e640";
}
.nc-icon-outline.users_man-23:before {
content: "\e641";
}
.nc-icon-outline.users_man-glasses:before {
content: "\e642";
}
.nc-icon-outline.users_mobile-contact:before {
content: "\e643";
}
.nc-icon-outline.users_multiple-11:before {
content: "\e644";
}
.nc-icon-outline.users_multiple-19:before {
content: "\e645";
}
.nc-icon-outline.users_network:before {
content: "\e646";
}
.nc-icon-outline.users_parent:before {
content: "\e647";
}
.nc-icon-outline.users_single-01:before {
content: "\e648";
}
.nc-icon-outline.users_single-02:before {
content: "\e649";
}
.nc-icon-outline.users_single-03:before {
content: "\e64a";
}
.nc-icon-outline.users_single-04:before {
content: "\e64b";
}
.nc-icon-outline.users_single-05:before {
content: "\e64c";
}
.nc-icon-outline.users_single-body:before {
content: "\e64d";
}
.nc-icon-outline.users_single-position:before {
content: "\e64e";
}
.nc-icon-outline.users_square-31:before {
content: "\e64f";
}
.nc-icon-outline.users_square-32:before {
content: "\e650";
}
.nc-icon-outline.users_square-33:before {
content: "\e651";
}
.nc-icon-outline.users_woman-21:before {
content: "\e652";
}
.nc-icon-outline.users_woman-24:before {
content: "\e653";
}
.nc-icon-outline.users_woman-25:before {
content: "\e654";
}
.nc-icon-outline.users_woman-34:before {
content: "\e655";
}
.nc-icon-outline.users_woman-35:before {
content: "\e656";
}
.nc-icon-outline.users_woman-man:before {
content: "\e657";
}
.nc-icon-outline.ui-1_analytics-88:before {
content: "\e658";
}
.nc-icon-outline.ui-1_analytics-89:before {
content: "\e659";
}
.nc-icon-outline.ui-1_attach-86:before {
content: "\e65a";
}
.nc-icon-outline.ui-1_attach-87:before {
content: "\e65b";
}
.nc-icon-outline.ui-1_bell-53:before {
content: "\e65c";
}
.nc-icon-outline.ui-1_bell-54:before {
content: "\e65d";
}
.nc-icon-outline.ui-1_bell-55:before {
content: "\e65e";
}
.nc-icon-outline.ui-1_bold-add:before {
content: "\e65f";
}
.nc-icon-outline.ui-1_bold-delete:before {
content: "\e660";
}
.nc-icon-outline.ui-1_bold-remove:before {
content: "\e661";
}
.nc-icon-outline.ui-1_bookmark-add:before {
content: "\e662";
}
.nc-icon-outline.ui-1_bookmark-remove:before {
content: "\e663";
}
.nc-icon-outline.ui-1_calendar-57:before {
content: "\e664";
}
.nc-icon-outline.ui-1_calendar-60:before {
content: "\e665";
}
.nc-icon-outline.ui-1_calendar-check-59:before {
content: "\e666";
}
.nc-icon-outline.ui-1_calendar-check-62:before {
content: "\e667";
}
.nc-icon-outline.ui-1_calendar-grid-58:before {
content: "\e668";
}
.nc-icon-outline.ui-1_calendar-grid-61:before {
content: "\e669";
}
.nc-icon-outline.ui-1_check-bold:before {
content: "\e66a";
}
.nc-icon-outline.ui-1_check-circle-07:before {
content: "\e66b";
}
.nc-icon-outline.ui-1_check-circle-08:before {
content: "\e66c";
}
.nc-icon-outline.ui-1_check-curve:before {
content: "\e66d";
}
.nc-icon-outline.ui-1_check-simple:before {
content: "\e66e";
}
.nc-icon-outline.ui-1_check-small:before {
content: "\e66f";
}
.nc-icon-outline.ui-1_check-square-09:before {
content: "\e670";
}
.nc-icon-outline.ui-1_check-square-11:before {
content: "\e671";
}
.nc-icon-outline.ui-1_check:before {
content: "\e672";
}
.nc-icon-outline.ui-1_circle-add:before {
content: "\e673";
}
.nc-icon-outline.ui-1_circle-bold-add:before {
content: "\e674";
}
.nc-icon-outline.ui-1_circle-bold-remove:before {
content: "\e675";
}
.nc-icon-outline.ui-1_circle-delete:before {
content: "\e676";
}
.nc-icon-outline.ui-1_circle-remove:before {
content: "\e677";
}
.nc-icon-outline.ui-1_dashboard-29:before {
content: "\e678";
}
.nc-icon-outline.ui-1_dashboard-30:before {
content: "\e679";
}
.nc-icon-outline.ui-1_dashboard-half:before {
content: "\e67a";
}
.nc-icon-outline.ui-1_dashboard-level:before {
content: "\e67b";
}
.nc-icon-outline.ui-1_database:before {
content: "\e67c";
}
.nc-icon-outline.ui-1_drop:before {
content: "\e67d";
}
.nc-icon-outline.ui-1_edit-71:before {
content: "\e67e";
}
.nc-icon-outline.ui-1_edit-72:before {
content: "\e67f";
}
.nc-icon-outline.ui-1_edit-73:before {
content: "\e680";
}
.nc-icon-outline.ui-1_edit-74:before {
content: "\e681";
}
.nc-icon-outline.ui-1_edit-75:before {
content: "\e682";
}
.nc-icon-outline.ui-1_edit-76:before {
content: "\e683";
}
.nc-icon-outline.ui-1_edit-77:before {
content: "\e684";
}
.nc-icon-outline.ui-1_edit-78:before {
content: "\e685";
}
.nc-icon-outline.ui-1_email-83:before {
content: "\e686";
}
.nc-icon-outline.ui-1_email-84:before {
content: "\e687";
}
.nc-icon-outline.ui-1_email-85:before {
content: "\e688";
}
.nc-icon-outline.ui-1_eye-17:before {
content: "\e689";
}
.nc-icon-outline.ui-1_eye-19:before {
content: "\e68a";
}
.nc-icon-outline.ui-1_eye-ban-18:before {
content: "\e68b";
}
.nc-icon-outline.ui-1_eye-ban-20:before {
content: "\e68c";
}
.nc-icon-outline.ui-1_flame:before {
content: "\e68d";
}
.nc-icon-outline.ui-1_home-51:before {
content: "\e68e";
}
.nc-icon-outline.ui-1_home-52:before {
content: "\e68f";
}
.nc-icon-outline.ui-1_home-minimal:before {
content: "\e690";
}
.nc-icon-outline.ui-1_home-simple:before {
content: "\e691";
}
.nc-icon-outline.ui-1_leaf-80:before {
content: "\e692";
}
.nc-icon-outline.ui-1_leaf-81:before {
content: "\e693";
}
.nc-icon-outline.ui-1_leaf-edit:before {
content: "\e694";
}
.nc-icon-outline.ui-1_lock-circle-open:before {
content: "\e695";
}
.nc-icon-outline.ui-1_lock-circle:before {
content: "\e696";
}
.nc-icon-outline.ui-1_lock-open:before {
content: "\e697";
}
.nc-icon-outline.ui-1_lock:before {
content: "\e698";
}
.nc-icon-outline.ui-1_notification-69:before {
content: "\e699";
}
.nc-icon-outline.ui-1_notification-70:before {
content: "\e69a";
}
.nc-icon-outline.ui-1_pencil:before {
content: "\e69b";
}
.nc-icon-outline.ui-1_preferences-circle-rotate:before {
content: "\e69c";
}
.nc-icon-outline.ui-1_preferences-circle:before {
content: "\e69d";
}
.nc-icon-outline.ui-1_preferences-container-circle-rotate:before {
content: "\e69e";
}
.nc-icon-outline.ui-1_preferences-container-circle:before {
content: "\e69f";
}
.nc-icon-outline.ui-1_preferences-container-rotate:before {
content: "\e6a0";
}
.nc-icon-outline.ui-1_preferences-container:before {
content: "\e6a1";
}
.nc-icon-outline.ui-1_preferences-rotate:before {
content: "\e6a2";
}
.nc-icon-outline.ui-1_preferences:before {
content: "\e6a3";
}
.nc-icon-outline.ui-1_send:before {
content: "\e6a4";
}
.nc-icon-outline.ui-1_settings-gear-63:before {
content: "\e6a5";
}
.nc-icon-outline.ui-1_settings-gear-64:before {
content: "\e6a6";
}
.nc-icon-outline.ui-1_settings-gear-65:before {
content: "\e6a7";
}
.nc-icon-outline.ui-1_settings-tool-66:before {
content: "\e6a8";
}
.nc-icon-outline.ui-1_settings-tool-67:before {
content: "\e6a9";
}
.nc-icon-outline.ui-1_settings:before {
content: "\e6aa";
}
.nc-icon-outline.ui-1_simple-add:before {
content: "\e6ab";
}
.nc-icon-outline.ui-1_simple-delete:before {
content: "\e6ac";
}
.nc-icon-outline.ui-1_simple-remove:before {
content: "\e6ad";
}
.nc-icon-outline.ui-1_trash-round:before {
content: "\e6ae";
}
.nc-icon-outline.ui-1_trash-simple:before {
content: "\e6af";
}
.nc-icon-outline.ui-1_trash:before {
content: "\e6b0";
}
.nc-icon-outline.ui-1_ui-03:before {
content: "\e6b1";
}
.nc-icon-outline.ui-1_ui-04:before {
content: "\e6b2";
}
.nc-icon-outline.ui-1_zoom-bold-in:before {
content: "\e6b3";
}
.nc-icon-outline.ui-1_zoom-bold-out:before {
content: "\e6b4";
}
.nc-icon-outline.ui-1_zoom-bold:before {
content: "\e6b5";
}
.nc-icon-outline.ui-1_zoom-in:before {
content: "\e6b6";
}
.nc-icon-outline.ui-1_zoom-out:before {
content: "\e6b7";
}
.nc-icon-outline.ui-1_zoom-split-in:before {
content: "\e6b8";
}
.nc-icon-outline.ui-1_zoom-split-out:before {
content: "\e6b9";
}
.nc-icon-outline.ui-1_zoom-split:before {
content: "\e6ba";
}
.nc-icon-outline.ui-1_zoom:before {
content: "\e6bb";
}
.nc-icon-outline.ui-2_alert:before {
content: "\e6bc";
}
.nc-icon-outline.ui-2_alert-:before {
content: "\e6bd";
}
.nc-icon-outline.ui-2_alert-circle:before {
content: "\e6be";
}
.nc-icon-outline.ui-2_alert-circle-:before {
content: "\e6bf";
}
.nc-icon-outline.ui-2_alert-circle-i:before {
content: "\e6c0";
}
.nc-icon-outline.ui-2_alert-i:before {
content: "\e6c1";
}
.nc-icon-outline.ui-2_alert-square:before {
content: "\e6c2";
}
.nc-icon-outline.ui-2_alert-square-:before {
content: "\e6c3";
}
.nc-icon-outline.ui-2_alert-square-i:before {
content: "\e6c4";
}
.nc-icon-outline.ui-2_archive:before {
content: "\e6c5";
}
.nc-icon-outline.ui-2_ban-bold:before {
content: "\e6c6";
}
.nc-icon-outline.ui-2_ban:before {
content: "\e6c7";
}
.nc-icon-outline.ui-2_battery-81:before {
content: "\e6c8";
}
.nc-icon-outline.ui-2_battery-83:before {
content: "\e6c9";
}
.nc-icon-outline.ui-2_battery-half:before {
content: "\e6ca";
}
.nc-icon-outline.ui-2_battery-low:before {
content: "\e6cb";
}
.nc-icon-outline.ui-2_bluetooth:before {
content: "\e6cc";
}
.nc-icon-outline.ui-2_book:before {
content: "\e6cd";
}
.nc-icon-outline.ui-2_chart-bar-52:before {
content: "\e6ce";
}
.nc-icon-outline.ui-2_chart-bar-53:before {
content: "\e6cf";
}
.nc-icon-outline.ui-2_chat-content:before {
content: "\e6d0";
}
.nc-icon-outline.ui-2_chat-round-content:before {
content: "\e6d1";
}
.nc-icon-outline.ui-2_chat-round:before {
content: "\e6d2";
}
.nc-icon-outline.ui-2_chat:before {
content: "\e6d3";
}
.nc-icon-outline.ui-2_circle-bold-delete:before {
content: "\e6d4";
}
.nc-icon-outline.ui-2_cloud-25:before {
content: "\e6d5";
}
.nc-icon-outline.ui-2_cloud-26:before {
content: "\e6d6";
}
.nc-icon-outline.ui-2_disk:before {
content: "\e6d7";
}
.nc-icon-outline.ui-2_enlarge-57:before {
content: "\e6d8";
}
.nc-icon-outline.ui-2_enlarge-58:before {
content: "\e6d9";
}
.nc-icon-outline.ui-2_enlarge-59:before {
content: "\e6da";
}
.nc-icon-outline.ui-2_fat-add:before {
content: "\e6db";
}
.nc-icon-outline.ui-2_fat-delete:before {
content: "\e6dc";
}
.nc-icon-outline.ui-2_fat-remove:before {
content: "\e6dd";
}
.nc-icon-outline.ui-2_favourite-28:before {
content: "\e6de";
}
.nc-icon-outline.ui-2_favourite-31:before {
content: "\e6df";
}
.nc-icon-outline.ui-2_favourite-add-29:before {
content: "\e6e0";
}
.nc-icon-outline.ui-2_favourite-add-32:before {
content: "\e6e1";
}
.nc-icon-outline.ui-2_favourite-remove-30:before {
content: "\e6e2";
}
.nc-icon-outline.ui-2_favourite-remove-33:before {
content: "\e6e3";
}
.nc-icon-outline.ui-2_filter:before {
content: "\e6e4";
}
.nc-icon-outline.ui-2_fullsize:before {
content: "\e6e5";
}
.nc-icon-outline.ui-2_grid-45:before {
content: "\e6e6";
}
.nc-icon-outline.ui-2_grid-46:before {
content: "\e6e7";
}
.nc-icon-outline.ui-2_grid-48:before {
content: "\e6e8";
}
.nc-icon-outline.ui-2_grid-49:before {
content: "\e6e9";
}
.nc-icon-outline.ui-2_grid-50:before {
content: "\e6ea";
}
.nc-icon-outline.ui-2_grid-square:before {
content: "\e6eb";
}
.nc-icon-outline.ui-2_hourglass:before {
content: "\e6ec";
}
.nc-icon-outline.ui-2_lab:before {
content: "\e6ed";
}
.nc-icon-outline.ui-2_layers:before {
content: "\e6ee";
}
.nc-icon-outline.ui-2_like:before {
content: "\e6ef";
}
.nc-icon-outline.ui-2_link-66:before {
content: "\e6f0";
}
.nc-icon-outline.ui-2_link-67:before {
content: "\e6f1";
}
.nc-icon-outline.ui-2_link-68:before {
content: "\e6f2";
}
.nc-icon-outline.ui-2_link-69:before {
content: "\e6f3";
}
.nc-icon-outline.ui-2_link-71:before {
content: "\e6f4";
}
.nc-icon-outline.ui-2_link-72:before {
content: "\e6f5";
}
.nc-icon-outline.ui-2_link-broken-70:before {
content: "\e6f6";
}
.nc-icon-outline.ui-2_link-broken-73:before {
content: "\e6f7";
}
.nc-icon-outline.ui-2_menu-34:before {
content: "\e6f8";
}
.nc-icon-outline.ui-2_menu-35:before {
content: "\e6f9";
}
.nc-icon-outline.ui-2_menu-bold:before {
content: "\e6fa";
}
.nc-icon-outline.ui-2_menu-dots:before {
content: "\e6fb";
}
.nc-icon-outline.ui-2_menu-square:before {
content: "\e6fc";
}
.nc-icon-outline.ui-2_node:before {
content: "\e6fd";
}
.nc-icon-outline.ui-2_paragraph:before {
content: "\e6fe";
}
.nc-icon-outline.ui-2_phone:before {
content: "\e6ff";
}
.nc-icon-outline.ui-2_settings-90:before {
content: "\e700";
}
.nc-icon-outline.ui-2_settings-91:before {
content: "\e701";
}
.nc-icon-outline.ui-2_share-bold:before {
content: "\e702";
}
.nc-icon-outline.ui-2_share:before {
content: "\e703";
}
.nc-icon-outline.ui-2_small-add:before {
content: "\e704";
}
.nc-icon-outline.ui-2_small-delete:before {
content: "\e705";
}
.nc-icon-outline.ui-2_small-remove:before {
content: "\e706";
}
.nc-icon-outline.ui-2_square-add-08:before {
content: "\e707";
}
.nc-icon-outline.ui-2_square-add-11:before {
content: "\e708";
}
.nc-icon-outline.ui-2_square-delete-10:before {
content: "\e709";
}
.nc-icon-outline.ui-2_square-delete-13:before {
content: "\e70a";
}
.nc-icon-outline.ui-2_square-remove-09:before {
content: "\e70b";
}
.nc-icon-outline.ui-2_square-remove-12:before {
content: "\e70c";
}
.nc-icon-outline.ui-2_target:before {
content: "\e70d";
}
.nc-icon-outline.ui-2_tile-55:before {
content: "\e70e";
}
.nc-icon-outline.ui-2_tile-56:before {
content: "\e70f";
}
.nc-icon-outline.ui-2_time-alarm:before {
content: "\e710";
}
.nc-icon-outline.ui-2_time-clock:before {
content: "\e711";
}
.nc-icon-outline.ui-2_time-countdown:before {
content: "\e712";
}
.nc-icon-outline.ui-2_time:before {
content: "\e713";
}
.nc-icon-outline.ui-2_webpage:before {
content: "\e714";
}
.nc-icon-outline.ui-2_window-add:before {
content: "\e715";
}
.nc-icon-outline.ui-2_window-delete:before {
content: "\e716";
}
.nc-icon-outline.transportation_air-baloon:before {
content: "\e717";
}
.nc-icon-outline.transportation_bike-sport:before {
content: "\e718";
}
.nc-icon-outline.transportation_bike:before {
content: "\e719";
}
.nc-icon-outline.transportation_boat-front:before {
content: "\e71a";
}
.nc-icon-outline.transportation_boat-small-02:before {
content: "\e71b";
}
.nc-icon-outline.transportation_boat-small-03:before {
content: "\e71c";
}
.nc-icon-outline.transportation_boat:before {
content: "\e71d";
}
.nc-icon-outline.transportation_bus-front-10:before {
content: "\e71e";
}
.nc-icon-outline.transportation_bus-front-12:before {
content: "\e71f";
}
.nc-icon-outline.transportation_bus:before {
content: "\e720";
}
.nc-icon-outline.transportation_car-front:before {
content: "\e721";
}
.nc-icon-outline.transportation_car-simple:before {
content: "\e722";
}
.nc-icon-outline.transportation_car-sport:before {
content: "\e723";
}
.nc-icon-outline.transportation_car-taxi:before {
content: "\e724";
}
.nc-icon-outline.transportation_car:before {
content: "\e725";
}
.nc-icon-outline.transportation_helicopter:before {
content: "\e726";
}
.nc-icon-outline.transportation_helmet:before {
content: "\e727";
}
.nc-icon-outline.transportation_light-traffic:before {
content: "\e728";
}
.nc-icon-outline.transportation_moto:before {
content: "\e729";
}
.nc-icon-outline.transportation_plane-17:before {
content: "\e72a";
}
.nc-icon-outline.transportation_plane-18:before {
content: "\e72b";
}
.nc-icon-outline.transportation_road:before {
content: "\e72c";
}
.nc-icon-outline.transportation_skateboard:before {
content: "\e72d";
}
.nc-icon-outline.transportation_tractor:before {
content: "\e72e";
}
.nc-icon-outline.transportation_train-speed:before {
content: "\e72f";
}
.nc-icon-outline.transportation_train:before {
content: "\e730";
}
.nc-icon-outline.transportation_tram:before {
content: "\e731";
}
.nc-icon-outline.transportation_truck-front:before {
content: "\e732";
}
.nc-icon-outline.transportation_vespa-front:before {
content: "\e733";
}
.nc-icon-outline.transportation_vespa:before {
content: "\e734";
}
.nc-icon-outline.tech_cable-49:before {
content: "\e735";
}
.nc-icon-outline.tech_cable-50:before {
content: "\e736";
}
.nc-icon-outline.tech_cd-reader:before {
content: "\e737";
}
.nc-icon-outline.tech_computer-monitor:before {
content: "\e738";
}
.nc-icon-outline.tech_computer-old:before {
content: "\e739";
}
.nc-icon-outline.tech_computer:before {
content: "\e73a";
}
.nc-icon-outline.tech_controller-modern:before {
content: "\e73b";
}
.nc-icon-outline.tech_controller:before {
content: "\e73c";
}
.nc-icon-outline.tech_desktop-screen:before {
content: "\e73d";
}
.nc-icon-outline.tech_desktop:before {
content: "\e73e";
}
.nc-icon-outline.tech_disk-reader:before {
content: "\e73f";
}
.nc-icon-outline.tech_disk:before {
content: "\e740";
}
.nc-icon-outline.tech_gopro:before {
content: "\e741";
}
.nc-icon-outline.tech_headphones:before {
content: "\e742";
}
.nc-icon-outline.tech_keyboard-mouse:before {
content: "\e743";
}
.nc-icon-outline.tech_keyboard-wifi:before {
content: "\e744";
}
.nc-icon-outline.tech_keyboard:before {
content: "\e745";
}
.nc-icon-outline.tech_laptop-camera:before {
content: "\e746";
}
.nc-icon-outline.tech_laptop-front:before {
content: "\e747";
}
.nc-icon-outline.tech_laptop:before {
content: "\e748";
}
.nc-icon-outline.tech_mobile-button:before {
content: "\e749";
}
.nc-icon-outline.tech_mobile-camera:before {
content: "\e74a";
}
.nc-icon-outline.tech_mobile-recharger-08:before {
content: "\e74b";
}
.nc-icon-outline.tech_mobile-recharger-09:before {
content: "\e74c";
}
.nc-icon-outline.tech_mobile-toolbar:before {
content: "\e74d";
}
.nc-icon-outline.tech_mobile:before {
content: "\e74e";
}
.nc-icon-outline.tech_music:before {
content: "\e74f";
}
.nc-icon-outline.tech_navigation:before {
content: "\e750";
}
.nc-icon-outline.tech_player-19:before {
content: "\e751";
}
.nc-icon-outline.tech_player-48:before {
content: "\e752";
}
.nc-icon-outline.tech_print-fold:before {
content: "\e753";
}
.nc-icon-outline.tech_print-round-fold:before {
content: "\e754";
}
.nc-icon-outline.tech_print-round:before {
content: "\e755";
}
.nc-icon-outline.tech_print:before {
content: "\e756";
}
.nc-icon-outline.tech_ram:before {
content: "\e757";
}
.nc-icon-outline.tech_remote:before {
content: "\e758";
}
.nc-icon-outline.tech_signal:before {
content: "\e759";
}
.nc-icon-outline.tech_socket:before {
content: "\e75a";
}
.nc-icon-outline.tech_sync:before {
content: "\e75b";
}
.nc-icon-outline.tech_tablet-button:before {
content: "\e75c";
}
.nc-icon-outline.tech_tablet-reader-31:before {
content: "\e75d";
}
.nc-icon-outline.tech_tablet-reader-42:before {
content: "\e75e";
}
.nc-icon-outline.tech_tablet-toolbar:before {
content: "\e75f";
}
.nc-icon-outline.tech_tablet:before {
content: "\e760";
}
.nc-icon-outline.tech_tv-old:before {
content: "\e761";
}
.nc-icon-outline.tech_tv:before {
content: "\e762";
}
.nc-icon-outline.tech_watch-circle:before {
content: "\e763";
}
.nc-icon-outline.tech_watch-time:before {
content: "\e764";
}
.nc-icon-outline.tech_watch:before {
content: "\e765";
}
.nc-icon-outline.tech_webcam-38:before {
content: "\e766";
}
.nc-icon-outline.tech_webcam-39:before {
content: "\e767";
}
.nc-icon-outline.tech_wifi-router:before {
content: "\e768";
}
.nc-icon-outline.tech_wifi:before {
content: "\e769";
}
.nc-icon-outline.shopping_award:before {
content: "\e76a";
}
.nc-icon-outline.shopping_bag-09:before {
content: "\e76b";
}
.nc-icon-outline.shopping_bag-16:before {
content: "\e76c";
}
.nc-icon-outline.shopping_bag-17:before {
content: "\e76d";
}
.nc-icon-outline.shopping_bag-20:before {
content: "\e76e";
}
.nc-icon-outline.shopping_bag-add-18:before {
content: "\e76f";
}
.nc-icon-outline.shopping_bag-add-21:before {
content: "\e770";
}
.nc-icon-outline.shopping_bag-edit:before {
content: "\e771";
}
.nc-icon-outline.shopping_bag-remove-19:before {
content: "\e772";
}
.nc-icon-outline.shopping_bag-remove-22:before {
content: "\e773";
}
.nc-icon-outline.shopping_barcode-scan:before {
content: "\e774";
}
.nc-icon-outline.shopping_barcode:before {
content: "\e775";
}
.nc-icon-outline.shopping_bardcode-qr:before {
content: "\e776";
}
.nc-icon-outline.shopping_basket-add:before {
content: "\e777";
}
.nc-icon-outline.shopping_basket-edit:before {
content: "\e778";
}
.nc-icon-outline.shopping_basket-remove:before {
content: "\e779";
}
.nc-icon-outline.shopping_basket-simple-add:before {
content: "\e77a";
}
.nc-icon-outline.shopping_basket-simple-remove:before {
content: "\e77b";
}
.nc-icon-outline.shopping_basket-simple:before {
content: "\e77c";
}
.nc-icon-outline.shopping_basket:before {
content: "\e77d";
}
.nc-icon-outline.shopping_bitcoin:before {
content: "\e77e";
}
.nc-icon-outline.shopping_board:before {
content: "\e77f";
}
.nc-icon-outline.shopping_box-3d-50:before {
content: "\e780";
}
.nc-icon-outline.shopping_box-3d-67:before {
content: "\e781";
}
.nc-icon-outline.shopping_box-ribbon:before {
content: "\e782";
}
.nc-icon-outline.shopping_box:before {
content: "\e783";
}
.nc-icon-outline.shopping_cart-add:before {
content: "\e784";
}
.nc-icon-outline.shopping_cart-modern-add:before {
content: "\e785";
}
.nc-icon-outline.shopping_cart-modern-in:before {
content: "\e786";
}
.nc-icon-outline.shopping_cart-modern-remove:before {
content: "\e787";
}
.nc-icon-outline.shopping_cart-modern:before {
content: "\e788";
}
.nc-icon-outline.shopping_cart-remove:before {
content: "\e789";
}
.nc-icon-outline.shopping_cart-simple-add:before {
content: "\e78a";
}
.nc-icon-outline.shopping_cart-simple-in:before {
content: "\e78b";
}
.nc-icon-outline.shopping_cart-simple-remove:before {
content: "\e78c";
}
.nc-icon-outline.shopping_cart-simple:before {
content: "\e78d";
}
.nc-icon-outline.shopping_cart:before {
content: "\e78e";
}
.nc-icon-outline.shopping_cash-register:before {
content: "\e78f";
}
.nc-icon-outline.shopping_chart:before {
content: "\e790";
}
.nc-icon-outline.shopping_credit-card-in:before {
content: "\e791";
}
.nc-icon-outline.shopping_credit-card:before {
content: "\e792";
}
.nc-icon-outline.shopping_credit-locked:before {
content: "\e793";
}
.nc-icon-outline.shopping_delivery-fast:before {
content: "\e794";
}
.nc-icon-outline.shopping_delivery-time:before {
content: "\e795";
}
.nc-icon-outline.shopping_delivery-track:before {
content: "\e796";
}
.nc-icon-outline.shopping_delivery:before {
content: "\e797";
}
.nc-icon-outline.shopping_discount:before {
content: "\e798";
}
.nc-icon-outline.shopping_gift:before {
content: "\e799";
}
.nc-icon-outline.shopping_hand-card:before {
content: "\e79a";
}
.nc-icon-outline.shopping_list:before {
content: "\e79b";
}
.nc-icon-outline.shopping_mobile-card:before {
content: "\e79c";
}
.nc-icon-outline.shopping_mobile-cart:before {
content: "\e79d";
}
.nc-icon-outline.shopping_mobile-touch:before {
content: "\e79e";
}
.nc-icon-outline.shopping_newsletter:before {
content: "\e79f";
}
.nc-icon-outline.shopping_pos:before {
content: "\e7a0";
}
.nc-icon-outline.shopping_receipt-list-42:before {
content: "\e7a1";
}
.nc-icon-outline.shopping_receipt-list-43:before {
content: "\e7a2";
}
.nc-icon-outline.shopping_receipt:before {
content: "\e7a3";
}
.nc-icon-outline.shopping_shop-location:before {
content: "\e7a4";
}
.nc-icon-outline.shopping_shop:before {
content: "\e7a5";
}
.nc-icon-outline.shopping_stock:before {
content: "\e7a6";
}
.nc-icon-outline.shopping_tag-content:before {
content: "\e7a7";
}
.nc-icon-outline.shopping_tag-cut:before {
content: "\e7a8";
}
.nc-icon-outline.shopping_tag-line:before {
content: "\e7a9";
}
.nc-icon-outline.shopping_tag-sale:before {
content: "\e7aa";
}
.nc-icon-outline.shopping_tag:before {
content: "\e7ab";
}
.nc-icon-outline.shopping_wallet:before {
content: "\e7ac";
}
.nc-icon-outline.education_abc:before {
content: "\e7ad";
}
.nc-icon-outline.education_agenda-bookmark:before {
content: "\e7ae";
}
.nc-icon-outline.education_atom:before {
content: "\e7af";
}
.nc-icon-outline.education_award-55:before {
content: "\e7b0";
}
.nc-icon-outline.education_backpack-57:before {
content: "\e7b1";
}
.nc-icon-outline.education_backpack-58:before {
content: "\e7b2";
}
.nc-icon-outline.education_ball-basket:before {
content: "\e7b3";
}
.nc-icon-outline.education_ball-soccer:before {
content: "\e7b4";
}
.nc-icon-outline.education_board-51:before {
content: "\e7b5";
}
.nc-icon-outline.education_book-39:before {
content: "\e7b6";
}
.nc-icon-outline.education_book-bookmark:before {
content: "\e7b7";
}
.nc-icon-outline.education_book-open:before {
content: "\e7b8";
}
.nc-icon-outline.education_books-46:before {
content: "\e7b9";
}
.nc-icon-outline.education_chalkboard:before {
content: "\e7ba";
}
.nc-icon-outline.education_flask:before {
content: "\e7bb";
}
.nc-icon-outline.education_glasses:before {
content: "\e7bc";
}
.nc-icon-outline.education_grammar-check:before {
content: "\e7bd";
}
.nc-icon-outline.education_hat:before {
content: "\e7be";
}
.nc-icon-outline.education_language:before {
content: "\e7bf";
}
.nc-icon-outline.education_microscope:before {
content: "\e7c0";
}
.nc-icon-outline.education_molecule:before {
content: "\e7c1";
}
.nc-icon-outline.education_notepad:before {
content: "\e7c2";
}
.nc-icon-outline.education_paper-diploma:before {
content: "\e7c3";
}
.nc-icon-outline.education_paper:before {
content: "\e7c4";
}
.nc-icon-outline.education_pencil-47:before {
content: "\e7c5";
}
.nc-icon-outline.education_school:before {
content: "\e7c6";
}
.nc-icon-outline.objects_alien-29:before {
content: "\e7c7";
}
.nc-icon-outline.objects_alien-33:before {
content: "\e7c8";
}
.nc-icon-outline.objects_anchor:before {
content: "\e7c9";
}
.nc-icon-outline.objects_astronaut:before {
content: "\e7ca";
}
.nc-icon-outline.objects_axe:before {
content: "\e7cb";
}
.nc-icon-outline.objects_baby-bottle:before {
content: "\e7cc";
}
.nc-icon-outline.objects_baby:before {
content: "\e7cd";
}
.nc-icon-outline.objects_baloon:before {
content: "\e7ce";
}
.nc-icon-outline.objects_battery:before {
content: "\e7cf";
}
.nc-icon-outline.objects_bear:before {
content: "\e7d0";
}
.nc-icon-outline.objects_billiard:before {
content: "\e7d1";
}
.nc-icon-outline.objects_binocular:before {
content: "\e7d2";
}
.nc-icon-outline.objects_bow:before {
content: "\e7d3";
}
.nc-icon-outline.objects_bowling:before {
content: "\e7d4";
}
.nc-icon-outline.objects_broom:before {
content: "\e7d5";
}
.nc-icon-outline.objects_cone:before {
content: "\e7d6";
}
.nc-icon-outline.objects_controller:before {
content: "\e7d7";
}
.nc-icon-outline.objects_diamond:before {
content: "\e7d8";
}
.nc-icon-outline.objects_dice:before {
content: "\e7d9";
}
.nc-icon-outline.objects_globe:before {
content: "\e7da";
}
.nc-icon-outline.objects_hut:before {
content: "\e7db";
}
.nc-icon-outline.objects_key-25:before {
content: "\e7dc";
}
.nc-icon-outline.objects_key-26:before {
content: "\e7dd";
}
.nc-icon-outline.objects_lamp:before {
content: "\e7de";
}
.nc-icon-outline.objects_leaf-36:before {
content: "\e7df";
}
.nc-icon-outline.objects_leaf-38:before {
content: "\e7e0";
}
.nc-icon-outline.objects_light:before {
content: "\e7e1";
}
.nc-icon-outline.objects_pipe:before {
content: "\e7e2";
}
.nc-icon-outline.objects_planet:before {
content: "\e7e3";
}
.nc-icon-outline.objects_puzzle-09:before {
content: "\e7e4";
}
.nc-icon-outline.objects_puzzle-10:before {
content: "\e7e5";
}
.nc-icon-outline.objects_shovel:before {
content: "\e7e6";
}
.nc-icon-outline.objects_skull:before {
content: "\e7e7";
}
.nc-icon-outline.objects_spaceship:before {
content: "\e7e8";
}
.nc-icon-outline.objects_spray:before {
content: "\e7e9";
}
.nc-icon-outline.objects_support-16:before {
content: "\e7ea";
}
.nc-icon-outline.objects_support-17:before {
content: "\e7eb";
}
.nc-icon-outline.objects_umbrella-13:before {
content: "\e7ec";
}
.nc-icon-outline.objects_umbrella-14:before {
content: "\e7ed";
}
.nc-icon-outline.objects_wool-ball:before {
content: "\e7ee";
}
.nc-icon-outline.media-1_3d:before {
content: "\e7ef";
}
.nc-icon-outline.media-1_action-73:before {
content: "\e7f0";
}
.nc-icon-outline.media-1_action-74:before {
content: "\e7f1";
}
.nc-icon-outline.media-1_album:before {
content: "\e7f2";
}
.nc-icon-outline.media-1_audio-91:before {
content: "\e7f3";
}
.nc-icon-outline.media-1_audio-92:before {
content: "\e7f4";
}
.nc-icon-outline.media-1_balance:before {
content: "\e7f5";
}
.nc-icon-outline.media-1_brightness-46:before {
content: "\e7f6";
}
.nc-icon-outline.media-1_brightness-47:before {
content: "\e7f7";
}
.nc-icon-outline.media-1_button-circle-pause:before {
content: "\e7f8";
}
.nc-icon-outline.media-1_button-circle-play:before {
content: "\e7f9";
}
.nc-icon-outline.media-1_button-circle-stop:before {
content: "\e7fa";
}
.nc-icon-outline.media-1_button-eject:before {
content: "\e7fb";
}
.nc-icon-outline.media-1_button-next:before {
content: "\e7fc";
}
.nc-icon-outline.media-1_button-pause:before {
content: "\e7fd";
}
.nc-icon-outline.media-1_button-play:before {
content: "\e7fe";
}
.nc-icon-outline.media-1_button-power:before {
content: "\e7ff";
}
.nc-icon-outline.media-1_button-previous:before {
content: "\e800";
}
.nc-icon-outline.media-1_button-record:before {
content: "\e801";
}
.nc-icon-outline.media-1_button-rewind:before {
content: "\e802";
}
.nc-icon-outline.media-1_button-skip:before {
content: "\e803";
}
.nc-icon-outline.media-1_button-stop:before {
content: "\e804";
}
.nc-icon-outline.media-1_camera-18:before {
content: "\e805";
}
.nc-icon-outline.media-1_camera-19:before {
content: "\e806";
}
.nc-icon-outline.media-1_camera-20:before {
content: "\e807";
}
.nc-icon-outline.media-1_camera-ban-36:before {
content: "\e808";
}
.nc-icon-outline.media-1_camera-ban-37:before {
content: "\e809";
}
.nc-icon-outline.media-1_camera-compact:before {
content: "\e80a";
}
.nc-icon-outline.media-1_camera-screen:before {
content: "\e80b";
}
.nc-icon-outline.media-1_camera-square-57:before {
content: "\e80c";
}
.nc-icon-outline.media-1_camera-square-58:before {
content: "\e80d";
}
.nc-icon-outline.media-1_camera-time:before {
content: "\e80e";
}
.nc-icon-outline.media-1_countdown-34:before {
content: "\e80f";
}
.nc-icon-outline.media-1_countdown-35:before {
content: "\e810";
}
.nc-icon-outline.media-1_edit-color:before {
content: "\e811";
}
.nc-icon-outline.media-1_edit-contrast-42:before {
content: "\e812";
}
.nc-icon-outline.media-1_edit-contrast-43:before {
content: "\e813";
}
.nc-icon-outline.media-1_edit-saturation:before {
content: "\e814";
}
.nc-icon-outline.media-1_flash-21:before {
content: "\e815";
}
.nc-icon-outline.media-1_flash-24:before {
content: "\e816";
}
.nc-icon-outline.media-1_flash-29:before {
content: "\e817";
}
.nc-icon-outline.media-1_flash-auto-22:before {
content: "\e818";
}
.nc-icon-outline.media-1_flash-auto-25:before {
content: "\e819";
}
.nc-icon-outline.media-1_flash-off-23:before {
content: "\e81a";
}
.nc-icon-outline.media-1_flash-off-26:before {
content: "\e81b";
}
.nc-icon-outline.media-1_focus-32:before {
content: "\e81c";
}
.nc-icon-outline.media-1_focus-38:before {
content: "\e81d";
}
.nc-icon-outline.media-1_focus-40:before {
content: "\e81e";
}
.nc-icon-outline.media-1_focus-circle:before {
content: "\e81f";
}
.nc-icon-outline.media-1_frame-12:before {
content: "\e820";
}
.nc-icon-outline.media-1_frame-41:before {
content: "\e821";
}
.nc-icon-outline.media-1_grid:before {
content: "\e822";
}
.nc-icon-outline.media-1_image-01:before {
content: "\e823";
}
.nc-icon-outline.media-1_image-02:before {
content: "\e824";
}
.nc-icon-outline.media-1_image-05:before {
content: "\e825";
}
.nc-icon-outline.media-1_image-add:before {
content: "\e826";
}
.nc-icon-outline.media-1_image-delete:before {
content: "\e827";
}
.nc-icon-outline.media-1_image-location:before {
content: "\e828";
}
.nc-icon-outline.media-1_kid:before {
content: "\e829";
}
.nc-icon-outline.media-1_layers:before {
content: "\e82a";
}
.nc-icon-outline.media-1_lens-31:before {
content: "\e82b";
}
.nc-icon-outline.media-1_lens-56:before {
content: "\e82c";
}
.nc-icon-outline.media-1_macro:before {
content: "\e82d";
}
.nc-icon-outline.media-1_movie-61:before {
content: "\e82e";
}
.nc-icon-outline.media-1_movie-62:before {
content: "\e82f";
}
.nc-icon-outline.media-1_night:before {
content: "\e830";
}
.nc-icon-outline.media-1_picture:before {
content: "\e831";
}
.nc-icon-outline.media-1_play-68:before {
content: "\e832";
}
.nc-icon-outline.media-1_play-69:before {
content: "\e833";
}
.nc-icon-outline.media-1_player:before {
content: "\e834";
}
.nc-icon-outline.media-1_polaroid-add:before {
content: "\e835";
}
.nc-icon-outline.media-1_polaroid-delete:before {
content: "\e836";
}
.nc-icon-outline.media-1_polaroid-multiple:before {
content: "\e837";
}
.nc-icon-outline.media-1_polaroid-user:before {
content: "\e838";
}
.nc-icon-outline.media-1_polaroid:before {
content: "\e839";
}
.nc-icon-outline.media-1_roll:before {
content: "\e83a";
}
.nc-icon-outline.media-1_rotate-left:before {
content: "\e83b";
}
.nc-icon-outline.media-1_rotate-right:before {
content: "\e83c";
}
.nc-icon-outline.media-1_sd:before {
content: "\e83d";
}
.nc-icon-outline.media-1_selfie:before {
content: "\e83e";
}
.nc-icon-outline.media-1_shake:before {
content: "\e83f";
}
.nc-icon-outline.media-1_speaker:before {
content: "\e840";
}
.nc-icon-outline.media-1_sport:before {
content: "\e841";
}
.nc-icon-outline.media-1_ticket-75:before {
content: "\e842";
}
.nc-icon-outline.media-1_ticket-76:before {
content: "\e843";
}
.nc-icon-outline.media-1_touch:before {
content: "\e844";
}
.nc-icon-outline.media-1_tripod:before {
content: "\e845";
}
.nc-icon-outline.media-1_video-64:before {
content: "\e846";
}
.nc-icon-outline.media-1_video-65:before {
content: "\e847";
}
.nc-icon-outline.media-1_video-66:before {
content: "\e848";
}
.nc-icon-outline.media-1_video-67:before {
content: "\e849";
}
.nc-icon-outline.media-1_videocamera-71:before {
content: "\e84a";
}
.nc-icon-outline.media-1_videocamera-72:before {
content: "\e84b";
}
.nc-icon-outline.media-1_volume-93:before {
content: "\e84c";
}
.nc-icon-outline.media-1_volume-97:before {
content: "\e84d";
}
.nc-icon-outline.media-1_volume-98:before {
content: "\e84e";
}
.nc-icon-outline.media-1_volume-ban:before {
content: "\e84f";
}
.nc-icon-outline.media-1_volume-down:before {
content: "\e850";
}
.nc-icon-outline.media-1_volume-off:before {
content: "\e851";
}
.nc-icon-outline.media-1_volume-up:before {
content: "\e852";
}
.nc-icon-outline.media-2_guitar:before {
content: "\e853";
}
.nc-icon-outline.media-2_headphones-mic:before {
content: "\e854";
}
.nc-icon-outline.media-2_headphones:before {
content: "\e855";
}
.nc-icon-outline.media-2_knob:before {
content: "\e856";
}
.nc-icon-outline.media-2_mic:before {
content: "\e857";
}
.nc-icon-outline.media-2_music-album:before {
content: "\e858";
}
.nc-icon-outline.media-2_music-cloud:before {
content: "\e859";
}
.nc-icon-outline.media-2_note-03:before {
content: "\e85a";
}
.nc-icon-outline.media-2_note-04:before {
content: "\e85b";
}
.nc-icon-outline.media-2_piano:before {
content: "\e85c";
}
.nc-icon-outline.media-2_radio:before {
content: "\e85d";
}
.nc-icon-outline.media-2_remix:before {
content: "\e85e";
}
.nc-icon-outline.media-2_sound-wave:before {
content: "\e85f";
}
.nc-icon-outline.media-2_speaker-01:before {
content: "\e860";
}
.nc-icon-outline.media-2_speaker-05:before {
content: "\e861";
}
.nc-icon-outline.media-2_tape:before {
content: "\e862";
}
.nc-icon-outline.location_appointment:before {
content: "\e863";
}
.nc-icon-outline.location_bookmark-add:before {
content: "\e864";
}
.nc-icon-outline.location_bookmark-remove:before {
content: "\e865";
}
.nc-icon-outline.location_bookmark:before {
content: "\e866";
}
.nc-icon-outline.location_compass-04:before {
content: "\e867";
}
.nc-icon-outline.location_compass-05:before {
content: "\e868";
}
.nc-icon-outline.location_compass-06:before {
content: "\e869";
}
.nc-icon-outline.location_crosshair:before {
content: "\e86a";
}
.nc-icon-outline.location_explore-user:before {
content: "\e86b";
}
.nc-icon-outline.location_explore:before {
content: "\e86c";
}
.nc-icon-outline.location_flag-complex:before {
content: "\e86d";
}
.nc-icon-outline.location_flag-diagonal-33:before {
content: "\e86e";
}
.nc-icon-outline.location_flag-diagonal-34:before {
content: "\e86f";
}
.nc-icon-outline.location_flag-points-31:before {
content: "\e870";
}
.nc-icon-outline.location_flag-points-32:before {
content: "\e871";
}
.nc-icon-outline.location_flag-simple:before {
content: "\e872";
}
.nc-icon-outline.location_flag-triangle:before {
content: "\e873";
}
.nc-icon-outline.location_flag:before {
content: "\e874";
}
.nc-icon-outline.location_gps:before {
content: "\e875";
}
.nc-icon-outline.location_map-big:before {
content: "\e876";
}
.nc-icon-outline.location_map-compass:before {
content: "\e877";
}
.nc-icon-outline.location_map-gps:before {
content: "\e878";
}
.nc-icon-outline.location_map-marker:before {
content: "\e879";
}
.nc-icon-outline.location_map-pin:before {
content: "\e87a";
}
.nc-icon-outline.location_map:before {
content: "\e87b";
}
.nc-icon-outline.location_marker:before {
content: "\e87c";
}
.nc-icon-outline.location_pin-add:before {
content: "\e87d";
}
.nc-icon-outline.location_pin-copy:before {
content: "\e87e";
}
.nc-icon-outline.location_pin-remove:before {
content: "\e87f";
}
.nc-icon-outline.location_pin:before {
content: "\e880";
}
.nc-icon-outline.location_pins:before {
content: "\e881";
}
.nc-icon-outline.location_position-marker:before {
content: "\e882";
}
.nc-icon-outline.location_position-pin:before {
content: "\e883";
}
.nc-icon-outline.location_position-user:before {
content: "\e884";
}
.nc-icon-outline.location_radar:before {
content: "\e885";
}
.nc-icon-outline.location_road:before {
content: "\e886";
}
.nc-icon-outline.location_route-alert:before {
content: "\e887";
}
.nc-icon-outline.location_route-close:before {
content: "\e888";
}
.nc-icon-outline.location_route-open:before {
content: "\e889";
}
.nc-icon-outline.location_square-marker:before {
content: "\e88a";
}
.nc-icon-outline.location_square-pin:before {
content: "\e88b";
}
.nc-icon-outline.location_treasure-map-21:before {
content: "\e88c";
}
.nc-icon-outline.location_treasure-map-40:before {
content: "\e88d";
}
.nc-icon-outline.location_worl-marker:before {
content: "\e88e";
}
.nc-icon-outline.location_world-pin:before {
content: "\e88f";
}
.nc-icon-outline.location_world:before {
content: "\e890";
}
.nc-icon-outline.health_ambulance:before {
content: "\e891";
}
.nc-icon-outline.health_apple:before {
content: "\e892";
}
.nc-icon-outline.health_bag-49:before {
content: "\e893";
}
.nc-icon-outline.health_bag-50:before {
content: "\e894";
}
.nc-icon-outline.health_brain:before {
content: "\e895";
}
.nc-icon-outline.health_dna-27:before {
content: "\e896";
}
.nc-icon-outline.health_dna-38:before {
content: "\e897";
}
.nc-icon-outline.health_doctor:before {
content: "\e898";
}
.nc-icon-outline.health_flask:before {
content: "\e899";
}
.nc-icon-outline.health_heartbeat-16:before {
content: "\e89a";
}
.nc-icon-outline.health_height:before {
content: "\e89b";
}
.nc-icon-outline.health_hospital-32:before {
content: "\e89c";
}
.nc-icon-outline.health_hospital-33:before {
content: "\e89d";
}
.nc-icon-outline.health_hospital-34:before {
content: "\e89e";
}
.nc-icon-outline.health_humidity-26:before {
content: "\e89f";
}
.nc-icon-outline.health_humidity-52:before {
content: "\e8a0";
}
.nc-icon-outline.health_intestine:before {
content: "\e8a1";
}
.nc-icon-outline.health_lungs:before {
content: "\e8a2";
}
.nc-icon-outline.health_molecule-39:before {
content: "\e8a3";
}
.nc-icon-outline.health_molecule-40:before {
content: "\e8a4";
}
.nc-icon-outline.health_notebook:before {
content: "\e8a5";
}
.nc-icon-outline.health_nurse:before {
content: "\e8a6";
}
.nc-icon-outline.health_patch-46:before {
content: "\e8a7";
}
.nc-icon-outline.health_pill-42:before {
content: "\e8a8";
}
.nc-icon-outline.health_pill-43:before {
content: "\e8a9";
}
.nc-icon-outline.health_pill-container-44:before {
content: "\e8aa";
}
.nc-icon-outline.health_pill-container-47:before {
content: "\e8ab";
}
.nc-icon-outline.health_pulse-chart:before {
content: "\e8ac";
}
.nc-icon-outline.health_pulse-phone:before {
content: "\e8ad";
}
.nc-icon-outline.health_pulse-sleep:before {
content: "\e8ae";
}
.nc-icon-outline.health_pulse-watch:before {
content: "\e8af";
}
.nc-icon-outline.health_pulse:before {
content: "\e8b0";
}
.nc-icon-outline.health_sleep:before {
content: "\e8b1";
}
.nc-icon-outline.health_steps:before {
content: "\e8b2";
}
.nc-icon-outline.health_syringe:before {
content: "\e8b3";
}
.nc-icon-outline.health_temperature-23:before {
content: "\e8b4";
}
.nc-icon-outline.health_temperature-24:before {
content: "\e8b5";
}
.nc-icon-outline.health_tooth:before {
content: "\e8b6";
}
.nc-icon-outline.health_weed:before {
content: "\e8b7";
}
.nc-icon-outline.health_weight:before {
content: "\e8b8";
}
.nc-icon-outline.health_wheelchair:before {
content: "\e8b9";
}
.nc-icon-outline.health_woman:before {
content: "\e8ba";
}
.nc-icon-outline.furniture_air-conditioner:before {
content: "\e8bb";
}
.nc-icon-outline.furniture_armchair:before {
content: "\e8bc";
}
.nc-icon-outline.furniture_bath-tub:before {
content: "\e8bd";
}
.nc-icon-outline.furniture_bed-09:before {
content: "\e8be";
}
.nc-icon-outline.furniture_bed-23:before {
content: "\e8bf";
}
.nc-icon-outline.furniture_bed-side:before {
content: "\e8c0";
}
.nc-icon-outline.furniture_cabinet:before {
content: "\e8c1";
}
.nc-icon-outline.furniture_cactus:before {
content: "\e8c2";
}
.nc-icon-outline.furniture_chair:before {
content: "\e8c3";
}
.nc-icon-outline.furniture_coat-hanger:before {
content: "\e8c4";
}
.nc-icon-outline.furniture_coffee:before {
content: "\e8c5";
}
.nc-icon-outline.furniture_cradle:before {
content: "\e8c6";
}
.nc-icon-outline.furniture_curtain:before {
content: "\e8c7";
}
.nc-icon-outline.furniture_desk-drawer:before {
content: "\e8c8";
}
.nc-icon-outline.furniture_desk:before {
content: "\e8c9";
}
.nc-icon-outline.furniture_door:before {
content: "\e8ca";
}
.nc-icon-outline.furniture_drawer:before {
content: "\e8cb";
}
.nc-icon-outline.furniture_fridge:before {
content: "\e8cc";
}
.nc-icon-outline.furniture_hanger-clothes:before {
content: "\e8cd";
}
.nc-icon-outline.furniture_hanger:before {
content: "\e8ce";
}
.nc-icon-outline.furniture_heater:before {
content: "\e8cf";
}
.nc-icon-outline.furniture_iron:before {
content: "\e8d0";
}
.nc-icon-outline.furniture_lamp-floor:before {
content: "\e8d1";
}
.nc-icon-outline.furniture_lamp:before {
content: "\e8d2";
}
.nc-icon-outline.furniture_library:before {
content: "\e8d3";
}
.nc-icon-outline.furniture_light:before {
content: "\e8d4";
}
.nc-icon-outline.furniture_mixer:before {
content: "\e8d5";
}
.nc-icon-outline.furniture_oven:before {
content: "\e8d6";
}
.nc-icon-outline.furniture_shower:before {
content: "\e8d7";
}
.nc-icon-outline.furniture_sink-wash:before {
content: "\e8d8";
}
.nc-icon-outline.furniture_sink:before {
content: "\e8d9";
}
.nc-icon-outline.furniture_sofa:before {
content: "\e8da";
}
.nc-icon-outline.furniture_storage-hanger:before {
content: "\e8db";
}
.nc-icon-outline.furniture_storage:before {
content: "\e8dc";
}
.nc-icon-outline.furniture_table:before {
content: "\e8dd";
}
.nc-icon-outline.furniture_toilet-paper:before {
content: "\e8de";
}
.nc-icon-outline.furniture_toilet:before {
content: "\e8df";
}
.nc-icon-outline.furniture_tv:before {
content: "\e8e0";
}
.nc-icon-outline.furniture_wardrobe:before {
content: "\e8e1";
}
.nc-icon-outline.furniture_wash:before {
content: "\e8e2";
}
.nc-icon-outline.files_add:before {
content: "\e8e3";
}
.nc-icon-outline.files_archive-3d-check:before {
content: "\e8e4";
}
.nc-icon-outline.files_archive-3d-content:before {
content: "\e8e5";
}
.nc-icon-outline.files_archive-check:before {
content: "\e8e6";
}
.nc-icon-outline.files_archive-content:before {
content: "\e8e7";
}
.nc-icon-outline.files_archive-paper-check:before {
content: "\e8e8";
}
.nc-icon-outline.files_archive-paper:before {
content: "\e8e9";
}
.nc-icon-outline.files_archive:before {
content: "\e8ea";
}
.nc-icon-outline.files_audio:before {
content: "\e8eb";
}
.nc-icon-outline.files_book-07:before {
content: "\e8ec";
}
.nc-icon-outline.files_book-08:before {
content: "\e8ed";
}
.nc-icon-outline.files_bookmark:before {
content: "\e8ee";
}
.nc-icon-outline.files_box:before {
content: "\e8ef";
}
.nc-icon-outline.files_chart-bar:before {
content: "\e8f0";
}
.nc-icon-outline.files_chart-pie:before {
content: "\e8f1";
}
.nc-icon-outline.files_check:before {
content: "\e8f2";
}
.nc-icon-outline.files_cloud:before {
content: "\e8f3";
}
.nc-icon-outline.files_copy:before {
content: "\e8f4";
}
.nc-icon-outline.files_dev:before {
content: "\e8f5";
}
.nc-icon-outline.files_download:before {
content: "\e8f6";
}
.nc-icon-outline.files_drawer:before {
content: "\e8f7";
}
.nc-icon-outline.files_edit:before {
content: "\e8f8";
}
.nc-icon-outline.files_exclamation:before {
content: "\e8f9";
}
.nc-icon-outline.files_folder-13:before {
content: "\e8fa";
}
.nc-icon-outline.files_folder-14:before {
content: "\e8fb";
}
.nc-icon-outline.files_folder-15:before {
content: "\e8fc";
}
.nc-icon-outline.files_folder-16:before {
content: "\e8fd";
}
.nc-icon-outline.files_folder-17:before {
content: "\e8fe";
}
.nc-icon-outline.files_folder-18:before {
content: "\e8ff";
}
.nc-icon-outline.files_folder-19:before {
content: "\e900";
}
.nc-icon-outline.files_folder-add:before {
content: "\e901";
}
.nc-icon-outline.files_folder-audio:before {
content: "\e902";
}
.nc-icon-outline.files_folder-bookmark:before {
content: "\e903";
}
.nc-icon-outline.files_folder-chart-bar:before {
content: "\e904";
}
.nc-icon-outline.files_folder-chart-pie:before {
content: "\e905";
}
.nc-icon-outline.files_folder-check:before {
content: "\e906";
}
.nc-icon-outline.files_folder-cloud:before {
content: "\e907";
}
.nc-icon-outline.files_folder-dev:before {
content: "\e908";
}
.nc-icon-outline.files_folder-download:before {
content: "\e909";
}
.nc-icon-outline.files_folder-edit:before {
content: "\e90a";
}
.nc-icon-outline.files_folder-exclamation:before {
content: "\e90b";
}
.nc-icon-outline.files_folder-gallery:before {
content: "\e90c";
}
.nc-icon-outline.files_folder-heart:before {
content: "\e90d";
}
.nc-icon-outline.files_folder-image:before {
content: "\e90e";
}
.nc-icon-outline.files_folder-info:before {
content: "\e90f";
}
.nc-icon-outline.files_folder-link:before {
content: "\e910";
}
.nc-icon-outline.files_folder-locked:before {
content: "\e911";
}
.nc-icon-outline.files_folder-money:before {
content: "\e912";
}
.nc-icon-outline.files_folder-music:before {
content: "\e913";
}
.nc-icon-outline.files_folder-no-access:before {
content: "\e914";
}
.nc-icon-outline.files_folder-play:before {
content: "\e915";
}
.nc-icon-outline.files_folder-question:before {
content: "\e916";
}
.nc-icon-outline.files_folder-refresh:before {
content: "\e917";
}
.nc-icon-outline.files_folder-remove:before {
content: "\e918";
}
.nc-icon-outline.files_folder-search:before {
content: "\e919";
}
.nc-icon-outline.files_folder-settings-81:before {
content: "\e91a";
}
.nc-icon-outline.files_folder-settings-97:before {
content: "\e91b";
}
.nc-icon-outline.files_folder-shared:before {
content: "\e91c";
}
.nc-icon-outline.files_folder-star:before {
content: "\e91d";
}
.nc-icon-outline.files_folder-time:before {
content: "\e91e";
}
.nc-icon-outline.files_folder-upload:before {
content: "\e91f";
}
.nc-icon-outline.files_folder-user:before {
content: "\e920";
}
.nc-icon-outline.files_folder-vector:before {
content: "\e921";
}
.nc-icon-outline.files_gallery:before {
content: "\e922";
}
.nc-icon-outline.files_heart:before {
content: "\e923";
}
.nc-icon-outline.files_image:before {
content: "\e924";
}
.nc-icon-outline.files_info:before {
content: "\e925";
}
.nc-icon-outline.files_link:before {
content: "\e926";
}
.nc-icon-outline.files_locked:before {
content: "\e927";
}
.nc-icon-outline.files_money:before {
content: "\e928";
}
.nc-icon-outline.files_music:before {
content: "\e929";
}
.nc-icon-outline.files_no-access:before {
content: "\e92a";
}
.nc-icon-outline.files_notebook:before {
content: "\e92b";
}
.nc-icon-outline.files_paper:before {
content: "\e92c";
}
.nc-icon-outline.files_play:before {
content: "\e92d";
}
.nc-icon-outline.files_question:before {
content: "\e92e";
}
.nc-icon-outline.files_refresh:before {
content: "\e92f";
}
.nc-icon-outline.files_remove:before {
content: "\e930";
}
.nc-icon-outline.files_replace-folder:before {
content: "\e931";
}
.nc-icon-outline.files_replace:before {
content: "\e932";
}
.nc-icon-outline.files_search:before {
content: "\e933";
}
.nc-icon-outline.files_settings-46:before {
content: "\e934";
}
.nc-icon-outline.files_settings-99:before {
content: "\e935";
}
.nc-icon-outline.files_shared:before {
content: "\e936";
}
.nc-icon-outline.files_single-content-02:before {
content: "\e937";
}
.nc-icon-outline.files_single-content-03:before {
content: "\e938";
}
.nc-icon-outline.files_single-copies:before {
content: "\e939";
}
.nc-icon-outline.files_single-copy-04:before {
content: "\e93a";
}
.nc-icon-outline.files_single-copy-06:before {
content: "\e93b";
}
.nc-icon-outline.files_single-folded-content:before {
content: "\e93c";
}
.nc-icon-outline.files_single-folded:before {
content: "\e93d";
}
.nc-icon-outline.files_single-paragraph:before {
content: "\e93e";
}
.nc-icon-outline.files_single:before {
content: "\e93f";
}
.nc-icon-outline.files_star:before {
content: "\e940";
}
.nc-icon-outline.files_time:before {
content: "\e941";
}
.nc-icon-outline.files_upload:before {
content: "\e942";
}
.nc-icon-outline.files_user:before {
content: "\e943";
}
.nc-icon-outline.files_vector:before {
content: "\e944";
}
.nc-icon-outline.files_zip-54:before {
content: "\e945";
}
.nc-icon-outline.files_zip-55:before {
content: "\e946";
}
.nc-icon-outline.design_album:before {
content: "\e947";
}
.nc-icon-outline.design_align-bottom:before {
content: "\e948";
}
.nc-icon-outline.design_align-center-horizontal:before {
content: "\e949";
}
.nc-icon-outline.design_align-center-vertical:before {
content: "\e94a";
}
.nc-icon-outline.design_align-left:before {
content: "\e94b";
}
.nc-icon-outline.design_align-right:before {
content: "\e94c";
}
.nc-icon-outline.design_align-top:before {
content: "\e94d";
}
.nc-icon-outline.design_app:before {
content: "\e94e";
}
.nc-icon-outline.design_artboard:before {
content: "\e94f";
}
.nc-icon-outline.design_blend:before {
content: "\e950";
}
.nc-icon-outline.design_book-bookmark:before {
content: "\e951";
}
.nc-icon-outline.design_book-open:before {
content: "\e952";
}
.nc-icon-outline.design_brush:before {
content: "\e953";
}
.nc-icon-outline.design_bug:before {
content: "\e954";
}
.nc-icon-outline.design_bullet-list-67:before {
content: "\e955";
}
.nc-icon-outline.design_bullet-list-68:before {
content: "\e956";
}
.nc-icon-outline.design_bullet-list-69:before {
content: "\e957";
}
.nc-icon-outline.design_bullet-list-70:before {
content: "\e958";
}
.nc-icon-outline.design_clone:before {
content: "\e959";
}
.nc-icon-outline.design_code-editor:before {
content: "\e95a";
}
.nc-icon-outline.design_code:before {
content: "\e95b";
}
.nc-icon-outline.design_collection:before {
content: "\e95c";
}
.nc-icon-outline.design_command:before {
content: "\e95d";
}
.nc-icon-outline.design_compass:before {
content: "\e95e";
}
.nc-icon-outline.design_contrast:before {
content: "\e95f";
}
.nc-icon-outline.design_copy:before {
content: "\e960";
}
.nc-icon-outline.design_crop:before {
content: "\e961";
}
.nc-icon-outline.design_cursor-48:before {
content: "\e962";
}
.nc-icon-outline.design_cursor-49:before {
content: "\e963";
}
.nc-icon-outline.design_design-dev:before {
content: "\e964";
}
.nc-icon-outline.design_design-responsive:before {
content: "\e965";
}
.nc-icon-outline.design_design:before {
content: "\e966";
}
.nc-icon-outline.design_distribute-horizontal:before {
content: "\e967";
}
.nc-icon-outline.design_distribute-vertical:before {
content: "\e968";
}
.nc-icon-outline.design_drag:before {
content: "\e969";
}
.nc-icon-outline.design_eraser-32:before {
content: "\e96a";
}
.nc-icon-outline.design_eraser-33:before {
content: "\e96b";
}
.nc-icon-outline.design_eraser-46:before {
content: "\e96c";
}
.nc-icon-outline.design_flip-horizontal:before {
content: "\e96d";
}
.nc-icon-outline.design_flip-vertical:before {
content: "\e96e";
}
.nc-icon-outline.design_image:before {
content: "\e96f";
}
.nc-icon-outline.design_magnet:before {
content: "\e970";
}
.nc-icon-outline.design_marker:before {
content: "\e971";
}
.nc-icon-outline.design_measure-02:before {
content: "\e972";
}
.nc-icon-outline.design_measure-17:before {
content: "\e973";
}
.nc-icon-outline.design_measure-big:before {
content: "\e974";
}
.nc-icon-outline.design_mobile-design:before {
content: "\e975";
}
.nc-icon-outline.design_mobile-dev:before {
content: "\e976";
}
.nc-icon-outline.design_mouse-08:before {
content: "\e977";
}
.nc-icon-outline.design_mouse-09:before {
content: "\e978";
}
.nc-icon-outline.design_mouse-10:before {
content: "\e979";
}
.nc-icon-outline.design_newsletter-dev:before {
content: "\e97a";
}
.nc-icon-outline.design_note-code:before {
content: "\e97b";
}
.nc-icon-outline.design_paint-16:before {
content: "\e97c";
}
.nc-icon-outline.design_paint-37:before {
content: "\e97d";
}
.nc-icon-outline.design_paint-38:before {
content: "\e97e";
}
.nc-icon-outline.design_paint-bucket-39:before {
content: "\e97f";
}
.nc-icon-outline.design_paint-bucket-40:before {
content: "\e980";
}
.nc-icon-outline.design_palette:before {
content: "\e981";
}
.nc-icon-outline.design_pantone:before {
content: "\e982";
}
.nc-icon-outline.design_paper-design:before {
content: "\e983";
}
.nc-icon-outline.design_paper-dev:before {
content: "\e984";
}
.nc-icon-outline.design_patch-19:before {
content: "\e985";
}
.nc-icon-outline.design_patch-34:before {
content: "\e986";
}
.nc-icon-outline.design_path-exclude:before {
content: "\e987";
}
.nc-icon-outline.design_path-intersect:before {
content: "\e988";
}
.nc-icon-outline.design_path-minus:before {
content: "\e989";
}
.nc-icon-outline.design_path-unite:before {
content: "\e98a";
}
.nc-icon-outline.design_pen-01:before {
content: "\e98b";
}
.nc-icon-outline.design_pen-23:before {
content: "\e98c";
}
.nc-icon-outline.design_pen-tool:before {
content: "\e98d";
}
.nc-icon-outline.design_phone:before {
content: "\e98e";
}
.nc-icon-outline.design_photo-editor:before {
content: "\e98f";
}
.nc-icon-outline.design_scissors-dashed:before {
content: "\e990";
}
.nc-icon-outline.design_scissors:before {
content: "\e991";
}
.nc-icon-outline.design_shape-adjust:before {
content: "\e992";
}
.nc-icon-outline.design_shape-circle:before {
content: "\e993";
}
.nc-icon-outline.design_shape-polygon:before {
content: "\e994";
}
.nc-icon-outline.design_shape-square:before {
content: "\e995";
}
.nc-icon-outline.design_shape-triangle:before {
content: "\e996";
}
.nc-icon-outline.design_shapes:before {
content: "\e997";
}
.nc-icon-outline.design_sharpener:before {
content: "\e998";
}
.nc-icon-outline.design_slice:before {
content: "\e999";
}
.nc-icon-outline.design_spray:before {
content: "\e99a";
}
.nc-icon-outline.design_stamp:before {
content: "\e99b";
}
.nc-icon-outline.design_tablet-mobile:before {
content: "\e99c";
}
.nc-icon-outline.design_tablet:before {
content: "\e99d";
}
.nc-icon-outline.design_text:before {
content: "\e99e";
}
.nc-icon-outline.design_todo:before {
content: "\e99f";
}
.nc-icon-outline.design_usb:before {
content: "\e9a0";
}
.nc-icon-outline.design_vector:before {
content: "\e9a1";
}
.nc-icon-outline.design_wand-11:before {
content: "\e9a2";
}
.nc-icon-outline.design_wand-99:before {
content: "\e9a3";
}
.nc-icon-outline.design_watch-dev:before {
content: "\e9a4";
}
.nc-icon-outline.design_web-design:before {
content: "\e9a5";
}
.nc-icon-outline.design_webpage:before {
content: "\e9a6";
}
.nc-icon-outline.design_window-code:before {
content: "\e9a7";
}
.nc-icon-outline.design_window-dev:before {
content: "\e9a8";
}
.nc-icon-outline.design_window-paragraph:before {
content: "\e9a9";
}
.nc-icon-outline.design_window-responsive:before {
content: "\e9aa";
}
.nc-icon-outline.clothes_baby:before {
content: "\e9ab";
}
.nc-icon-outline.clothes_backpack:before {
content: "\e9ac";
}
.nc-icon-outline.clothes_bag-21:before {
content: "\e9ad";
}
.nc-icon-outline.clothes_bag-22:before {
content: "\e9ae";
}
.nc-icon-outline.clothes_belt:before {
content: "\e9af";
}
.nc-icon-outline.clothes_boot-woman:before {
content: "\e9b0";
}
.nc-icon-outline.clothes_boot:before {
content: "\e9b1";
}
.nc-icon-outline.clothes_bra:before {
content: "\e9b2";
}
.nc-icon-outline.clothes_button:before {
content: "\e9b3";
}
.nc-icon-outline.clothes_cap:before {
content: "\e9b4";
}
.nc-icon-outline.clothes_coat:before {
content: "\e9b5";
}
.nc-icon-outline.clothes_corset:before {
content: "\e9b6";
}
.nc-icon-outline.clothes_dress-man:before {
content: "\e9b7";
}
.nc-icon-outline.clothes_dress-woman:before {
content: "\e9b8";
}
.nc-icon-outline.clothes_flip:before {
content: "\e9b9";
}
.nc-icon-outline.clothes_glasses:before {
content: "\e9ba";
}
.nc-icon-outline.clothes_gloves:before {
content: "\e9bb";
}
.nc-icon-outline.clothes_hat-top:before {
content: "\e9bc";
}
.nc-icon-outline.clothes_hat:before {
content: "\e9bd";
}
.nc-icon-outline.clothes_hoodie:before {
content: "\e9be";
}
.nc-icon-outline.clothes_iron-dont:before {
content: "\e9bf";
}
.nc-icon-outline.clothes_iron:before {
content: "\e9c0";
}
.nc-icon-outline.clothes_jeans-41:before {
content: "\e9c1";
}
.nc-icon-outline.clothes_jeans-43:before {
content: "\e9c2";
}
.nc-icon-outline.clothes_jeans-pocket:before {
content: "\e9c3";
}
.nc-icon-outline.clothes_kitchen:before {
content: "\e9c4";
}
.nc-icon-outline.clothes_long-sleeve:before {
content: "\e9c5";
}
.nc-icon-outline.clothes_makeup:before {
content: "\e9c6";
}
.nc-icon-outline.clothes_needle:before {
content: "\e9c7";
}
.nc-icon-outline.clothes_pajamas:before {
content: "\e9c8";
}
.nc-icon-outline.clothes_ring:before {
content: "\e9c9";
}
.nc-icon-outline.clothes_scarf:before {
content: "\e9ca";
}
.nc-icon-outline.clothes_shirt-business:before {
content: "\e9cb";
}
.nc-icon-outline.clothes_shirt-buttons:before {
content: "\e9cc";
}
.nc-icon-outline.clothes_shirt-neck:before {
content: "\e9cd";
}
.nc-icon-outline.clothes_shirt:before {
content: "\e9ce";
}
.nc-icon-outline.clothes_shoe-man:before {
content: "\e9cf";
}
.nc-icon-outline.clothes_shoe-sport:before {
content: "\e9d0";
}
.nc-icon-outline.clothes_shoe-woman:before {
content: "\e9d1";
}
.nc-icon-outline.clothes_skirt:before {
content: "\e9d2";
}
.nc-icon-outline.clothes_slacks-12:before {
content: "\e9d3";
}
.nc-icon-outline.clothes_slacks-13:before {
content: "\e9d4";
}
.nc-icon-outline.clothes_sock:before {
content: "\e9d5";
}
.nc-icon-outline.clothes_tie-bow:before {
content: "\e9d6";
}
.nc-icon-outline.clothes_tshirt-53:before {
content: "\e9d7";
}
.nc-icon-outline.clothes_tshirt-54:before {
content: "\e9d8";
}
.nc-icon-outline.clothes_tshirt-sport:before {
content: "\e9d9";
}
.nc-icon-outline.clothes_underwear-man:before {
content: "\e9da";
}
.nc-icon-outline.clothes_underwear:before {
content: "\e9db";
}
.nc-icon-outline.clothes_vest-sport:before {
content: "\e9dc";
}
.nc-icon-outline.clothes_vest:before {
content: "\e9dd";
}
.nc-icon-outline.clothes_wash-30:before {
content: "\e9de";
}
.nc-icon-outline.clothes_wash-60:before {
content: "\e9df";
}
.nc-icon-outline.clothes_wash-90:before {
content: "\e9e0";
}
.nc-icon-outline.clothes_wash-hand:before {
content: "\e9e1";
}
.nc-icon-outline.clothes_wash:before {
content: "\e9e2";
}
.nc-icon-outline.business_agenda:before {
content: "\e9e3";
}
.nc-icon-outline.business_atm:before {
content: "\e9e4";
}
.nc-icon-outline.business_award-48:before {
content: "\e9e5";
}
.nc-icon-outline.business_award-49:before {
content: "\e9e6";
}
.nc-icon-outline.business_award-74:before {
content: "\e9e7";
}
.nc-icon-outline.business_badge:before {
content: "\e9e8";
}
.nc-icon-outline.business_bank:before {
content: "\e9e9";
}
.nc-icon-outline.business_board-27:before {
content: "\e9ea";
}
.nc-icon-outline.business_board-28:before {
content: "\e9eb";
}
.nc-icon-outline.business_board-29:before {
content: "\e9ec";
}
.nc-icon-outline.business_board-30:before {
content: "\e9ed";
}
.nc-icon-outline.business_books:before {
content: "\e9ee";
}
.nc-icon-outline.business_briefcase-24:before {
content: "\e9ef";
}
.nc-icon-outline.business_briefcase-25:before {
content: "\e9f0";
}
.nc-icon-outline.business_briefcase-26:before {
content: "\e9f1";
}
.nc-icon-outline.business_building:before {
content: "\e9f2";
}
.nc-icon-outline.business_bulb-61:before {
content: "\e9f3";
}
.nc-icon-outline.business_bulb-62:before {
content: "\e9f4";
}
.nc-icon-outline.business_bulb-63:before {
content: "\e9f5";
}
.nc-icon-outline.business_business-contact-85:before {
content: "\e9f6";
}
.nc-icon-outline.business_business-contact-86:before {
content: "\e9f7";
}
.nc-icon-outline.business_business-contact-87:before {
content: "\e9f8";
}
.nc-icon-outline.business_business-contact-88:before {
content: "\e9f9";
}
.nc-icon-outline.business_business-contact-89:before {
content: "\e9fa";
}
.nc-icon-outline.business_businessman-03:before {
content: "\e9fb";
}
.nc-icon-outline.business_businessman-04:before {
content: "\e9fc";
}
.nc-icon-outline.business_calculator:before {
content: "\e9fd";
}
.nc-icon-outline.business_chair:before {
content: "\e9fe";
}
.nc-icon-outline.business_chart-bar-32:before {
content: "\e9ff";
}
.nc-icon-outline.business_chart-bar-33:before {
content: "\ea00";
}
.nc-icon-outline.business_chart-growth:before {
content: "\ea01";
}
.nc-icon-outline.business_chart-pie-35:before {
content: "\ea02";
}
.nc-icon-outline.business_chart-pie-36:before {
content: "\ea03";
}
.nc-icon-outline.business_chart:before {
content: "\ea04";
}
.nc-icon-outline.business_cheque:before {
content: "\ea05";
}
.nc-icon-outline.business_coins:before {
content: "\ea06";
}
.nc-icon-outline.business_connect:before {
content: "\ea07";
}
.nc-icon-outline.business_contacts:before {
content: "\ea08";
}
.nc-icon-outline.business_currency-dollar:before {
content: "\ea09";
}
.nc-icon-outline.business_currency-euro:before {
content: "\ea0a";
}
.nc-icon-outline.business_currency-pound:before {
content: "\ea0b";
}
.nc-icon-outline.business_currency-yen:before {
content: "\ea0c";
}
.nc-icon-outline.business_factory:before {
content: "\ea0d";
}
.nc-icon-outline.business_globe:before {
content: "\ea0e";
}
.nc-icon-outline.business_goal-64:before {
content: "\ea0f";
}
.nc-icon-outline.business_goal-65:before {
content: "\ea10";
}
.nc-icon-outline.business_gold:before {
content: "\ea11";
}
.nc-icon-outline.business_hammer:before {
content: "\ea12";
}
.nc-icon-outline.business_handout:before {
content: "\ea13";
}
.nc-icon-outline.business_handshake:before {
content: "\ea14";
}
.nc-icon-outline.business_hat:before {
content: "\ea15";
}
.nc-icon-outline.business_hierarchy-53:before {
content: "\ea16";
}
.nc-icon-outline.business_hierarchy-54:before {
content: "\ea17";
}
.nc-icon-outline.business_hierarchy-55:before {
content: "\ea18";
}
.nc-icon-outline.business_hierarchy-56:before {
content: "\ea19";
}
.nc-icon-outline.business_laptop-71:before {
content: "\ea1a";
}
.nc-icon-outline.business_laptop-72:before {
content: "\ea1b";
}
.nc-icon-outline.business_laptop-91:before {
content: "\ea1c";
}
.nc-icon-outline.business_law:before {
content: "\ea1d";
}
.nc-icon-outline.business_math:before {
content: "\ea1e";
}
.nc-icon-outline.business_money-11:before {
content: "\ea1f";
}
.nc-icon-outline.business_money-12:before {
content: "\ea20";
}
.nc-icon-outline.business_money-13:before {
content: "\ea21";
}
.nc-icon-outline.business_money-bag:before {
content: "\ea22";
}
.nc-icon-outline.business_money-coins:before {
content: "\ea23";
}
.nc-icon-outline.business_money-growth:before {
content: "\ea24";
}
.nc-icon-outline.business_money-time:before {
content: "\ea25";
}
.nc-icon-outline.business_net:before {
content: "\ea26";
}
.nc-icon-outline.business_notes:before {
content: "\ea27";
}
.nc-icon-outline.business_payment:before {
content: "\ea28";
}
.nc-icon-outline.business_percentage-38:before {
content: "\ea29";
}
.nc-icon-outline.business_percentage-39:before {
content: "\ea2a";
}
.nc-icon-outline.business_pig:before {
content: "\ea2b";
}
.nc-icon-outline.business_pin:before {
content: "\ea2c";
}
.nc-icon-outline.business_plug:before {
content: "\ea2d";
}
.nc-icon-outline.business_progress:before {
content: "\ea2e";
}
.nc-icon-outline.business_round-dollar:before {
content: "\ea2f";
}
.nc-icon-outline.business_round-euro:before {
content: "\ea30";
}
.nc-icon-outline.business_round-pound:before {
content: "\ea31";
}
.nc-icon-outline.business_round-yen:before {
content: "\ea32";
}
.nc-icon-outline.business_safe:before {
content: "\ea33";
}
.nc-icon-outline.business_scale:before {
content: "\ea34";
}
.nc-icon-outline.business_sign:before {
content: "\ea35";
}
.nc-icon-outline.business_signature:before {
content: "\ea36";
}
.nc-icon-outline.business_stock:before {
content: "\ea37";
}
.nc-icon-outline.business_strategy:before {
content: "\ea38";
}
.nc-icon-outline.business_tie-01:before {
content: "\ea39";
}
.nc-icon-outline.business_tie-02:before {
content: "\ea3a";
}
.nc-icon-outline.business_wallet-43:before {
content: "\ea3b";
}
.nc-icon-outline.business_wallet-44:before {
content: "\ea3c";
}
.nc-icon-outline.business_wallet-90:before {
content: "\ea3d";
}
.nc-icon-outline.arrows-1_back-78:before {
content: "\ea3e";
}
.nc-icon-outline.arrows-1_back-80:before {
content: "\ea3f";
}
.nc-icon-outline.arrows-1_bold-direction:before {
content: "\ea40";
}
.nc-icon-outline.arrows-1_bold-down:before {
content: "\ea41";
}
.nc-icon-outline.arrows-1_bold-left:before {
content: "\ea42";
}
.nc-icon-outline.arrows-1_bold-right:before {
content: "\ea43";
}
.nc-icon-outline.arrows-1_bold-up:before {
content: "\ea44";
}
.nc-icon-outline.arrows-1_circle-down-12:before {
content: "\ea45";
}
.nc-icon-outline.arrows-1_circle-down-40:before {
content: "\ea46";
}
.nc-icon-outline.arrows-1_circle-left-10:before {
content: "\ea47";
}
.nc-icon-outline.arrows-1_circle-left-38:before {
content: "\ea48";
}
.nc-icon-outline.arrows-1_circle-right-09:before {
content: "\ea49";
}
.nc-icon-outline.arrows-1_circle-right-37:before {
content: "\ea4a";
}
.nc-icon-outline.arrows-1_circle-up-11:before {
content: "\ea4b";
}
.nc-icon-outline.arrows-1_circle-up-39:before {
content: "\ea4c";
}
.nc-icon-outline.arrows-1_cloud-download-93:before {
content: "\ea4d";
}
.nc-icon-outline.arrows-1_cloud-download-95:before {
content: "\ea4e";
}
.nc-icon-outline.arrows-1_cloud-upload-94:before {
content: "\ea4f";
}
.nc-icon-outline.arrows-1_cloud-upload-96:before {
content: "\ea50";
}
.nc-icon-outline.arrows-1_curved-next:before {
content: "\ea51";
}
.nc-icon-outline.arrows-1_curved-previous:before {
content: "\ea52";
}
.nc-icon-outline.arrows-1_direction-53:before {
content: "\ea53";
}
.nc-icon-outline.arrows-1_direction-56:before {
content: "\ea54";
}
.nc-icon-outline.arrows-1_double-left:before {
content: "\ea55";
}
.nc-icon-outline.arrows-1_double-right:before {
content: "\ea56";
}
.nc-icon-outline.arrows-1_download:before {
content: "\ea57";
}
.nc-icon-outline.arrows-1_enlarge-diagonal-43:before {
content: "\ea58";
}
.nc-icon-outline.arrows-1_enlarge-diagonal-44:before {
content: "\ea59";
}
.nc-icon-outline.arrows-1_enlarge-horizontal:before {
content: "\ea5a";
}
.nc-icon-outline.arrows-1_enlarge-vertical:before {
content: "\ea5b";
}
.nc-icon-outline.arrows-1_fit-horizontal:before {
content: "\ea5c";
}
.nc-icon-outline.arrows-1_fit-vertical:before {
content: "\ea5d";
}
.nc-icon-outline.arrows-1_fullscreen-70:before {
content: "\ea5e";
}
.nc-icon-outline.arrows-1_fullscreen-71:before {
content: "\ea5f";
}
.nc-icon-outline.arrows-1_fullscreen-76:before {
content: "\ea60";
}
.nc-icon-outline.arrows-1_fullscreen-77:before {
content: "\ea61";
}
.nc-icon-outline.arrows-1_fullscreen-double-74:before {
content: "\ea62";
}
.nc-icon-outline.arrows-1_fullscreen-double-75:before {
content: "\ea63";
}
.nc-icon-outline.arrows-1_fullscreen-split-72:before {
content: "\ea64";
}
.nc-icon-outline.arrows-1_fullscreen-split-73:before {
content: "\ea65";
}
.nc-icon-outline.arrows-1_log-in:before {
content: "\ea66";
}
.nc-icon-outline.arrows-1_log-out:before {
content: "\ea67";
}
.nc-icon-outline.arrows-1_loop-82:before {
content: "\ea68";
}
.nc-icon-outline.arrows-1_loop-83:before {
content: "\ea69";
}
.nc-icon-outline.arrows-1_minimal-down:before {
content: "\ea6a";
}
.nc-icon-outline.arrows-1_minimal-left:before {
content: "\ea6b";
}
.nc-icon-outline.arrows-1_minimal-right:before {
content: "\ea6c";
}
.nc-icon-outline.arrows-1_minimal-up:before {
content: "\ea6d";
}
.nc-icon-outline.arrows-1_redo-79:before {
content: "\ea6e";
}
.nc-icon-outline.arrows-1_redo-81:before {
content: "\ea6f";
}
.nc-icon-outline.arrows-1_refresh-68:before {
content: "\ea70";
}
.nc-icon-outline.arrows-1_refresh-69:before {
content: "\ea71";
}
.nc-icon-outline.arrows-1_round-down:before {
content: "\ea72";
}
.nc-icon-outline.arrows-1_round-left:before {
content: "\ea73";
}
.nc-icon-outline.arrows-1_round-right:before {
content: "\ea74";
}
.nc-icon-outline.arrows-1_round-up:before {
content: "\ea75";
}
.nc-icon-outline.arrows-1_share-66:before {
content: "\ea76";
}
.nc-icon-outline.arrows-1_share-91:before {
content: "\ea77";
}
.nc-icon-outline.arrows-1_share-92:before {
content: "\ea78";
}
.nc-icon-outline.arrows-1_shuffle-97:before {
content: "\ea79";
}
.nc-icon-outline.arrows-1_shuffle-98:before {
content: "\ea7a";
}
.nc-icon-outline.arrows-1_simple-down:before {
content: "\ea7b";
}
.nc-icon-outline.arrows-1_simple-left:before {
content: "\ea7c";
}
.nc-icon-outline.arrows-1_simple-right:before {
content: "\ea7d";
}
.nc-icon-outline.arrows-1_simple-up:before {
content: "\ea7e";
}
.nc-icon-outline.arrows-1_small-triangle-down:before {
content: "\ea7f";
}
.nc-icon-outline.arrows-1_small-triangle-left:before {
content: "\ea80";
}
.nc-icon-outline.arrows-1_small-triangle-right:before {
content: "\ea81";
}
.nc-icon-outline.arrows-1_small-triangle-up:before {
content: "\ea82";
}
.nc-icon-outline.arrows-1_square-down:before {
content: "\ea83";
}
.nc-icon-outline.arrows-1_square-left:before {
content: "\ea84";
}
.nc-icon-outline.arrows-1_square-right:before {
content: "\ea85";
}
.nc-icon-outline.arrows-1_square-up:before {
content: "\ea86";
}
.nc-icon-outline.arrows-1_strong-down:before {
content: "\ea87";
}
.nc-icon-outline.arrows-1_strong-left:before {
content: "\ea88";
}
.nc-icon-outline.arrows-1_strong-right:before {
content: "\ea89";
}
.nc-icon-outline.arrows-1_strong-up:before {
content: "\ea8a";
}
.nc-icon-outline.arrows-1_tail-down:before {
content: "\ea8b";
}
.nc-icon-outline.arrows-1_tail-left:before {
content: "\ea8c";
}
.nc-icon-outline.arrows-1_tail-right:before {
content: "\ea8d";
}
.nc-icon-outline.arrows-1_tail-triangle-down:before {
content: "\ea8e";
}
.nc-icon-outline.arrows-1_tail-triangle-left:before {
content: "\ea8f";
}
.nc-icon-outline.arrows-1_tail-triangle-right:before {
content: "\ea90";
}
.nc-icon-outline.arrows-1_tail-triangle-up:before {
content: "\ea91";
}
.nc-icon-outline.arrows-1_tail-up:before {
content: "\ea92";
}
.nc-icon-outline.arrows-1_trend-down:before {
content: "\ea93";
}
.nc-icon-outline.arrows-1_trend-up:before {
content: "\ea94";
}
.nc-icon-outline.arrows-1_triangle-down-20:before {
content: "\ea95";
}
.nc-icon-outline.arrows-1_triangle-down-65:before {
content: "\ea96";
}
.nc-icon-outline.arrows-1_triangle-left-18:before {
content: "\ea97";
}
.nc-icon-outline.arrows-1_triangle-left-63:before {
content: "\ea98";
}
.nc-icon-outline.arrows-1_triangle-right-17:before {
content: "\ea99";
}
.nc-icon-outline.arrows-1_triangle-right-62:before {
content: "\ea9a";
}
.nc-icon-outline.arrows-1_triangle-up-19:before {
content: "\ea9b";
}
.nc-icon-outline.arrows-1_triangle-up-64:before {
content: "\ea9c";
}
.nc-icon-outline.arrows-1_window-zoom-in:before {
content: "\ea9d";
}
.nc-icon-outline.arrows-1_window-zoom-out:before {
content: "\ea9e";
}
.nc-icon-outline.arrows-1_zoom-88:before {
content: "\ea9f";
}
.nc-icon-outline.arrows-1_zoom-99:before {
content: "\eaa0";
}
.nc-icon-outline.arrows-1_zoom-100:before {
content: "\eaa1";
}
.nc-icon-outline.arrows-2_block-down:before {
content: "\eaa2";
}
.nc-icon-outline.arrows-2_block-left:before {
content: "\eaa3";
}
.nc-icon-outline.arrows-2_block-right:before {
content: "\eaa4";
}
.nc-icon-outline.arrows-2_block-up:before {
content: "\eaa5";
}
.nc-icon-outline.arrows-2_circle-in:before {
content: "\eaa6";
}
.nc-icon-outline.arrows-2_circle-out:before {
content: "\eaa7";
}
.nc-icon-outline.arrows-2_circuit-round:before {
content: "\eaa8";
}
.nc-icon-outline.arrows-2_circuit:before {
content: "\eaa9";
}
.nc-icon-outline.arrows-2_computer-upload:before {
content: "\eaaa";
}
.nc-icon-outline.arrows-2_conversion:before {
content: "\eaab";
}
.nc-icon-outline.arrows-2_corner-down-round:before {
content: "\eaac";
}
.nc-icon-outline.arrows-2_corner-down:before {
content: "\eaad";
}
.nc-icon-outline.arrows-2_corner-left-down:before {
content: "\eaae";
}
.nc-icon-outline.arrows-2_corner-left-round:before {
content: "\eaaf";
}
.nc-icon-outline.arrows-2_corner-left:before {
content: "\eab0";
}
.nc-icon-outline.arrows-2_corner-right-down:before {
content: "\eab1";
}
.nc-icon-outline.arrows-2_corner-right-round:before {
content: "\eab2";
}
.nc-icon-outline.arrows-2_corner-right:before {
content: "\eab3";
}
.nc-icon-outline.arrows-2_corner-up-left:before {
content: "\eab4";
}
.nc-icon-outline.arrows-2_corner-up-right:before {
content: "\eab5";
}
.nc-icon-outline.arrows-2_corner-up-round:before {
content: "\eab6";
}
.nc-icon-outline.arrows-2_corner-up:before {
content: "\eab7";
}
.nc-icon-outline.arrows-2_cross-down:before {
content: "\eab8";
}
.nc-icon-outline.arrows-2_cross-horizontal:before {
content: "\eab9";
}
.nc-icon-outline.arrows-2_cross-left:before {
content: "\eaba";
}
.nc-icon-outline.arrows-2_cross-right:before {
content: "\eabb";
}
.nc-icon-outline.arrows-2_cross-up:before {
content: "\eabc";
}
.nc-icon-outline.arrows-2_cross-vertical:before {
content: "\eabd";
}
.nc-icon-outline.arrows-2_curve-circuit:before {
content: "\eabe";
}
.nc-icon-outline.arrows-2_curve-directions:before {
content: "\eabf";
}
.nc-icon-outline.arrows-2_curve-split:before {
content: "\eac0";
}
.nc-icon-outline.arrows-2_delete-49:before {
content: "\eac1";
}
.nc-icon-outline.arrows-2_delete-50:before {
content: "\eac2";
}
.nc-icon-outline.arrows-2_direction:before {
content: "\eac3";
}
.nc-icon-outline.arrows-2_dots-download:before {
content: "\eac4";
}
.nc-icon-outline.arrows-2_dots-upload:before {
content: "\eac5";
}
.nc-icon-outline.arrows-2_eject:before {
content: "\eac6";
}
.nc-icon-outline.arrows-2_enlarge-circle:before {
content: "\eac7";
}
.nc-icon-outline.arrows-2_file-download-87:before {
content: "\eac8";
}
.nc-icon-outline.arrows-2_file-download-89:before {
content: "\eac9";
}
.nc-icon-outline.arrows-2_file-download-94:before {
content: "\eaca";
}
.nc-icon-outline.arrows-2_file-upload-86:before {
content: "\eacb";
}
.nc-icon-outline.arrows-2_file-upload-88:before {
content: "\eacc";
}
.nc-icon-outline.arrows-2_file-upload-93:before {
content: "\eacd";
}
.nc-icon-outline.arrows-2_fork-round:before {
content: "\eace";
}
.nc-icon-outline.arrows-2_fork:before {
content: "\eacf";
}
.nc-icon-outline.arrows-2_hit-down:before {
content: "\ead0";
}
.nc-icon-outline.arrows-2_hit-left:before {
content: "\ead1";
}
.nc-icon-outline.arrows-2_hit-right:before {
content: "\ead2";
}
.nc-icon-outline.arrows-2_hit-up:before {
content: "\ead3";
}
.nc-icon-outline.arrows-2_lines:before {
content: "\ead4";
}
.nc-icon-outline.arrows-2_log-out:before {
content: "\ead5";
}
.nc-icon-outline.arrows-2_loop:before {
content: "\ead6";
}
.nc-icon-outline.arrows-2_merge-round:before {
content: "\ead7";
}
.nc-icon-outline.arrows-2_merge:before {
content: "\ead8";
}
.nc-icon-outline.arrows-2_move-05:before {
content: "\ead9";
}
.nc-icon-outline.arrows-2_move-06:before {
content: "\eada";
}
.nc-icon-outline.arrows-2_move-92:before {
content: "\eadb";
}
.nc-icon-outline.arrows-2_move-down-right:before {
content: "\eadc";
}
.nc-icon-outline.arrows-2_move-down:before {
content: "\eadd";
}
.nc-icon-outline.arrows-2_move-left:before {
content: "\eade";
}
.nc-icon-outline.arrows-2_move-right:before {
content: "\eadf";
}
.nc-icon-outline.arrows-2_move-up-left:before {
content: "\eae0";
}
.nc-icon-outline.arrows-2_move-up:before {
content: "\eae1";
}
.nc-icon-outline.arrows-2_push-next:before {
content: "\eae2";
}
.nc-icon-outline.arrows-2_push-previous:before {
content: "\eae3";
}
.nc-icon-outline.arrows-2_reload:before {
content: "\eae4";
}
.nc-icon-outline.arrows-2_replay:before {
content: "\eae5";
}
.nc-icon-outline.arrows-2_rotate-left:before {
content: "\eae6";
}
.nc-icon-outline.arrows-2_rotate-right:before {
content: "\eae7";
}
.nc-icon-outline.arrows-2_round-left-down:before {
content: "\eae8";
}
.nc-icon-outline.arrows-2_round-right-down:before {
content: "\eae9";
}
.nc-icon-outline.arrows-2_round-up-left:before {
content: "\eaea";
}
.nc-icon-outline.arrows-2_round-up-right:before {
content: "\eaeb";
}
.nc-icon-outline.arrows-2_select-83:before {
content: "\eaec";
}
.nc-icon-outline.arrows-2_select-84:before {
content: "\eaed";
}
.nc-icon-outline.arrows-2_separate-round:before {
content: "\eaee";
}
.nc-icon-outline.arrows-2_separate:before {
content: "\eaef";
}
.nc-icon-outline.arrows-2_share-left:before {
content: "\eaf0";
}
.nc-icon-outline.arrows-2_share-right:before {
content: "\eaf1";
}
.nc-icon-outline.arrows-2_skew-down:before {
content: "\eaf2";
}
.nc-icon-outline.arrows-2_skew-left:before {
content: "\eaf3";
}
.nc-icon-outline.arrows-2_skew-right:before {
content: "\eaf4";
}
.nc-icon-outline.arrows-2_skew-up:before {
content: "\eaf5";
}
.nc-icon-outline.arrows-2_small-left:before {
content: "\eaf6";
}
.nc-icon-outline.arrows-2_small-right:before {
content: "\eaf7";
}
.nc-icon-outline.arrows-2_split-horizontal:before {
content: "\eaf8";
}
.nc-icon-outline.arrows-2_split-round:before {
content: "\eaf9";
}
.nc-icon-outline.arrows-2_split-vertical:before {
content: "\eafa";
}
.nc-icon-outline.arrows-2_split:before {
content: "\eafb";
}
.nc-icon-outline.arrows-2_square-download:before {
content: "\eafc";
}
.nc-icon-outline.arrows-2_square-upload:before {
content: "\eafd";
}
.nc-icon-outline.arrows-2_time:before {
content: "\eafe";
}
.nc-icon-outline.arrows-2_triangle-down:before {
content: "\eaff";
}
.nc-icon-outline.arrows-2_triangle-left:before {
content: "\eb00";
}
.nc-icon-outline.arrows-2_triangle-right:before {
content: "\eb01";
}
.nc-icon-outline.arrows-2_triangle-up:before {
content: "\eb02";
}
.nc-icon-outline.arrows-2_unite-round:before {
content: "\eb03";
}
.nc-icon-outline.arrows-2_unite:before {
content: "\eb04";
}
.nc-icon-outline.arrows-2_zoom:before {
content: "\eb05";
}
.nc-icon-outline.arrows-3_circle-down:before {
content: "\eb06";
}
.nc-icon-outline.arrows-3_circle-left:before {
content: "\eb07";
}
.nc-icon-outline.arrows-3_circle-right:before {
content: "\eb08";
}
.nc-icon-outline.arrows-3_circle-simple-down:before {
content: "\eb09";
}
.nc-icon-outline.arrows-3_circle-simple-left:before {
content: "\eb0a";
}
.nc-icon-outline.arrows-3_circle-simple-right:before {
content: "\eb0b";
}
.nc-icon-outline.arrows-3_circle-simple-up:before {
content: "\eb0c";
}
.nc-icon-outline.arrows-3_circle-up:before {
content: "\eb0d";
}
.nc-icon-outline.arrows-3_cloud-refresh:before {
content: "\eb0e";
}
.nc-icon-outline.arrows-3_separate:before {
content: "\eb0f";
}
.nc-icon-outline.arrows-3_small-down:before {
content: "\eb10";
}
.nc-icon-outline.arrows-3_small-up:before {
content: "\eb11";
}
.nc-icon-outline.arrows-3_square-corner-down-left-17:before {
content: "\eb12";
}
.nc-icon-outline.arrows-3_square-corner-down-left-18:before {
content: "\eb13";
}
.nc-icon-outline.arrows-3_square-corner-up-left:before {
content: "\eb14";
}
.nc-icon-outline.arrows-3_square-corner-up-right:before {
content: "\eb15";
}
.nc-icon-outline.arrows-3_square-down-06:before {
content: "\eb16";
}
.nc-icon-outline.arrows-3_square-down-22:before {
content: "\eb17";
}
.nc-icon-outline.arrows-3_square-enlarge:before {
content: "\eb18";
}
.nc-icon-outline.arrows-3_square-left-04:before {
content: "\eb19";
}
.nc-icon-outline.arrows-3_square-left-20:before {
content: "\eb1a";
}
.nc-icon-outline.arrows-3_square-right-03:before {
content: "\eb1b";
}
.nc-icon-outline.arrows-3_square-right-19:before {
content: "\eb1c";
}
.nc-icon-outline.arrows-3_square-simple-down:before {
content: "\eb1d";
}
.nc-icon-outline.arrows-3_square-simple-left:before {
content: "\eb1e";
}
.nc-icon-outline.arrows-3_square-simple-right:before {
content: "\eb1f";
}
.nc-icon-outline.arrows-3_square-simple-up:before {
content: "\eb20";
}
.nc-icon-outline.arrows-3_square-up-05:before {
content: "\eb21";
}
.nc-icon-outline.arrows-3_square-up-21:before {
content: "\eb22";
}
.nc-icon-outline.arrows-3_square-zoom:before {
content: "\eb23";
}
.nc-icon-outline.arrows-3_super-bold-down:before {
content: "\eb24";
}
.nc-icon-outline.arrows-3_super-bold-left:before {
content: "\eb25";
}
.nc-icon-outline.arrows-3_super-bold-right:before {
content: "\eb26";
}
.nc-icon-outline.arrows-3_super-bold-up:before {
content: "\eb27";
}
================================================
FILE: lib/shared/styles/nucleo/outline/less/mixins.less
================================================
.nc-rotate(@degrees, @rotation) {
filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=@rotation);
-webkit-transform: rotate(@degrees);
-moz-transform: rotate(@degrees);
-ms-transform: rotate(@degrees);
-o-transform: rotate(@degrees);
transform: rotate(@degrees);
}
.nc-flip(@horiz, @vert, @rotation) {
filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=@rotation);
-webkit-transform: scale(@horiz, @vert);
-moz-transform: scale(@horiz, @vert);
-ms-transform: scale(@horiz, @vert);
-o-transform: scale(@horiz, @vert);
transform: scale(@horiz, @vert);
}
================================================
FILE: lib/shared/styles/nucleo/outline/less/nucleo-outline.less
================================================
/* --------------------------------
Nucleo Outline Web Font - nucleoapp.com/
License - nucleoapp.com/license/
Created using IcoMoon - icomoon.io
-------------------------------- */
@import "./variables.less";
@font-face {
font-family: 'Nucleo Outline';
src: url('/fonts/nucleo-outline.eot');
src: url('/fonts/nucleo-outline.eot') format('embedded-opentype'),
url('/fonts/nucleo-outline.woff2') format('woff2'),
url('/fonts/nucleo-outline.woff') format('woff'),
url('/fonts/nucleo-outline.ttf') format('truetype'),
url('/fonts/nucleo-outline.svg') format('svg');
font-weight: normal;
font-style: normal;
}
:global {
/*------------------------
base class definition
-------------------------*/
.nc-icon-outline {
display: inline-block;
font: normal normal normal @nc-font-size-base/1 'Nucleo Outline';
font-size: inherit;
speak: none;
text-transform: none;
/* Better Font Rendering */
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
@import "./icons.less";
}
================================================
FILE: lib/shared/styles/nucleo/outline/less/variables.less
================================================
@nc-font-path: "../fonts";
@nc-font-size-base: 14px;
@nc-background-color: #eee;
@nc-li-width: (30em / 14);
@nc-padding-width: (1em/3);
================================================
FILE: lib/shared/styles/sizes.less
================================================
@topMenuHeight: 45px;
@tabsHeight: 20px;
@actionsHeight: 25px;
@menuWidth: 290px;
================================================
FILE: migrations/.gitempty
================================================
================================================
FILE: package.json
================================================
{
"name": "relax",
"version": "0.0.0",
"description": "New generation CMS on top of React and Node.js",
"license": "GPL-3.0",
"repository": {
"type": "git",
"url": "https://github.com/relax/relax.git"
},
"scripts": {
"build": "NODE_ENV=production ./node_modules/.bin/webpack --output-public-path /public/js/ && cp -r assets/images/* public/images",
"start": "NODE_ENV=production ./node_modules/.bin/babel-node app",
"dev": "./node_modules/.bin/parallelshell 'npm run webpackServer' 'npm run watchServer'",
"watchServer": "./node_modules/.bin/nodemon -e js,jsx,json -w app.js -w lib -i lib/client -x './node_modules/webpack/bin/webpack.js --config ./webpack/webpack.node.config.js --progress && node build/app'",
"webpackServer": "node_modules/.bin/webpack-dev-server --config ./webpack/webpack.browser.config.js --inline --hot --progress --display-error-details",
"lint": "node_modules/.bin/eslint lib/** --quiet"
},
"dependencies": {
"babel-polyfill": "^6.3.14",
"body-parser": "^1.13.3",
"classnames": "^2.1.3",
"colr": "^1.2.2",
"connect-mongo": "^0.8.2",
"dropzone": "git://github.com/relax/dropzone.git",
"express": "^4.13.3",
"express-graphql": "^0.4.0",
"express-session": "^1.11.3",
"file-size": "^1.0.0",
"graphql": "^0.4.7",
"history": "1.17.0",
"hoist-non-react-statics": "^1.0.3",
"invariant": "^2.2.0",
"js-md5": "^0.3.0",
"kerberos": "0.0.17",
"keymaster": "git://github.com/relax/keymaster.git",
"lodash.clone": "^3.0.3",
"lodash.clonedeep": "^3.0.2",
"lodash.debounce": "^3.1.1",
"lodash.filter": "^3.1.1",
"lodash.find": "^3.2.1",
"lodash.findindex": "^4.2.0",
"lodash.foreach": "^3.0.3",
"lodash.merge": "^3.3.2",
"lodash.omit": "^3.1.0",
"lodash.sortby": "^3.1.5",
"medium-editor": "git://github.com/relax/medium-editor.git",
"mkdirp": "^0.5.1",
"moment": "^2.10.6",
"mongoose": "^4.1.12",
"morgan": "^1.6.1",
"multer": "^1.0.6",
"nodemailer": "^1.5.0",
"passport": "^0.3.0",
"passport-local": "^1.0.0",
"passport-local-mongoose": "^3.1.0",
"q": "^1.4.1",
"query-string": "^3.0.0",
"rc": "^1.1.1",
"react": "^0.14.0",
"react-colorpicker": "git://github.com/relax/react-colorpicker.git#relay",
"react-counter": "git://github.com/relax/react-counter.git#relay",
"react-dom": "^0.14.0",
"react-dropzone": "^3.2.2",
"react-gemini-scrollbar": "^2.0.1",
"react-google-maps": "^4.6.0",
"react-redux": "^4.4.0",
"react-router": "^1.0.2",
"redux": "^3.3.1",
"redux-combine-actions": "^0.1.2",
"redux-logger": "^2.0.4",
"redux-router": "1.0.0-beta5",
"redux-thunk": "^1.0.0",
"relate-js": "^0.2.7",
"relax-fragments": "0.1.1",
"relax-jss": "0.2.0",
"rimraf": "^2.4.3",
"semver": "^5.0.3",
"serialize-javascript": "^1.1.2",
"sharp": "^0.12.0",
"slug": "^0.9.1",
"soundmanager2": "git://github.com/relax/SoundManager2.git",
"superagent": "^1.6.1",
"touch": "^1.0.0",
"velocity-animate": "git://github.com/relax/velocity.git",
"warning": "^2.1.0",
"winston": "^2.1.0"
},
"devDependencies": {
"autoprefixer-loader": "^3.1.0",
"babel-core": "^6.4.5",
"babel-eslint": "^6.0.2",
"babel-loader": "^6.2.2",
"babel-plugin-react-transform": "^2.0.0",
"babel-plugin-transform-decorators-legacy": "^1.3.4",
"babel-preset-es2015": "^6.3.13",
"babel-preset-react": "^6.3.13",
"babel-preset-stage-0": "^6.3.13",
"css-loader": "^0.23.1",
"eslint": "^2.8.0",
"eslint-config-airbnb": "^7.0.0",
"eslint-plugin-react": "^4.2.2",
"extract-text-webpack-plugin": "^0.9.1",
"file-loader": "^0.8.4",
"json-loader": "^0.5.3",
"less": "^2.6.0",
"less-loader": "^2.2.2",
"nodemon": "^1.8.1",
"parallelshell": "^2.0.0",
"react-transform-catch-errors": "^1.0.2",
"react-transform-hmr": "^1.0.2",
"redbox-react": "^1.2.0",
"source-map-support": "^0.4.0",
"style-loader": "^0.13.0",
"url-loader": "^0.5.6",
"webpack": "^1.12.6",
"webpack-dev-server": "^1.12.1",
"webpack-hot-middleware": "^2.5.0"
},
"apps": [
{
"name": "relax",
"script": "index.js",
"instances": 1,
"exec_mode": "cluster",
"error_file": "../shared/logs/stderr.log",
"out_file": "../shared/logs/stdout.log",
"pid_file": "../shared/pids/pid",
"merge_logs": true,
"env": {
"NODE_ENV": "production"
}
}
],
"deploy": {
"test": {
"user": "node",
"host": "178.62.64.146",
"path": "/home/node/apps/relax",
"ref": "origin/master",
"repo": "https://github.com/relax/relax.git",
"post-deploy": ". /home/node/.nvm/nvm.sh && npm install && npm run build && pm2 startOrGracefulReload package.json --env production"
}
}
}
================================================
FILE: uploads/.gitignore
================================================
# Ignore everything in this directory
*
# Except this file
!.gitignore
================================================
FILE: webpack/webpack.browser.config.js
================================================
var webpack = require('webpack');
var ExtractTextPlugin = require('extract-text-webpack-plugin');
var config = require('../config');
var NoErrorsPlugin = webpack.NoErrorsPlugin;
var optimize = webpack.optimize;
var webpackConfig = module.exports = {
entry: {
admin: ['./lib/client/admin.js'],
auth: ['./lib/client/auth.js'],
public: ['./lib/client/public.js']
},
output: {
path: './public/js',
filename: '[name].js',
publicPath: 'http://localhost:' + config.devPort + '/js/'
},
resolve: {
modulesDirectories: ['shared', 'node_modules'],
extensions: ['', '.js', '.jsx', '.json']
},
plugins: [
new optimize.OccurenceOrderPlugin(),
new optimize.CommonsChunkPlugin('common.js', ['admin', 'auth', 'public'])
],
module: {
loaders: [
{
test: /\.(js|jsx)$/,
loader: 'babel',
exclude: /node_modules/,
query: {
cacheDirectory: true,
presets: ['react', 'es2015', 'stage-0'],
plugins: [
['transform-decorators-legacy'],
['react-transform', {
transforms: [
{
transform: 'react-transform-hmr',
imports: ['react'],
locals: ['module']
},
{
transform: 'react-transform-catch-errors',
imports: ['react', 'redbox-react']
}
]
}]
]
}
},
{
test: /\.json$/,
loader: 'json'
},
{
test: /\.(png|jpg|jpeg|gif)(\?v=[0-9]\.[0-9]\.[0-9])?$/,
loader: 'url'
},
{
test: /\.(woff|woff2|ttf|eot|svg)$/,
loader: 'file-loader?name=fonts/[name].[ext]'
}
]
},
devServer: {
port: config.devPort,
contentBase: 'http://localhost:' + config.port
}
};
if (process.env.NODE_ENV === 'production') {
webpackConfig.plugins.push(new ExtractTextPlugin('../css/[name].css'));
webpackConfig.module.loaders.push({
test: /\.(css)$/,
loader: ExtractTextPlugin.extract(
'style-loader',
'css-loader?modules&importLoaders=1&localIdentName=[name]__[local]___[hash:base64:5]!autoprefixer!postcss-loader',
{
publicPath: '../css/'
}
)
});
webpackConfig.plugins.push(
new webpack.optimize.UglifyJsPlugin({
mangle: true,
compress: {
sequences: true,
dead_code: true,
conditionals: true,
booleans: true,
unused: true,
if_return: true,
join_vars: true,
drop_console: true,
warnings: false
}
}),
new webpack.optimize.DedupePlugin(),
new webpack.DefinePlugin({
'process.env': {
NODE_ENV: JSON.stringify('production')
}
}),
new NoErrorsPlugin()
);
webpackConfig.devtool = 'source-map';
} else {
webpackConfig.module.loaders.push({
test: /\.(css|less)$/,
loader: 'style!css-loader?modules&importLoaders=1&localIdentName=[name]__[local]___[hash:base64:5]!less'
});
webpackConfig.devtool = 'eval';
}
================================================
FILE: webpack/webpack.node.config.js
================================================
var webpack = require('webpack');
var path = require('path');
var fs = require('fs');
var nodeModules = {};
fs.readdirSync('node_modules')
.filter(function (x) {
return ['.bin'].indexOf(x) === -1;
})
.forEach(function (mod) {
nodeModules[mod] = 'commonjs ' + mod;
});
module.exports = {
entry: './app.js',
target: 'node',
output: {
path: path.join(__dirname, '..', 'build'),
filename: 'app.js'
},
resolve: {
modulesDirectories: ['shared', 'node_modules'],
extensions: ['', '.js', '.jsx', '.json']
},
externals: nodeModules,
plugins: [
new webpack.BannerPlugin('require("source-map-support").install();', {
raw: true,
entryOnly: false
})
],
devtool: 'sourcemap',
module: {
loaders: [
{
test: /\.(js|jsx)$/,
loader: 'babel',
exclude: /node_modules/,
query: {
cacheDirectory: true,
presets: ['react', 'es2015', 'stage-0'],
plugins: [
['transform-decorators-legacy'],
['react-transform', {
transforms: [
{
transform: 'react-transform-catch-errors',
imports: ['react', 'redbox-react']
}
]
}]
]
}
},
{
test: /\.(css|less)$/,
loader: 'css-loader!less'
},
{
test: /\.(woff|woff2|ttf|eot|svg|png|jpg|jpeg|gif)(\?v=[0-9]\.[0-9]\.[0-9])?$/,
loader: 'url'
}
]
}
};