├── example
├── img
│ └── .gitignore
├── .gitattributes
├── tile.png
├── favicon.ico
├── tile-wide.png
├── css
│ ├── home.css
│ ├── main.css
│ └── normalize.css
├── robots.txt
├── apple-touch-icon.png
├── .gitignore
├── .editorconfig
├── browserconfig.xml
├── crossdomain.xml
├── js
│ ├── plugins.js
│ ├── main.jsx
│ ├── residue.js
│ ├── example_settings.jsx
│ └── bipyridine.js
├── webpack.config.js
├── index.html
└── .htaccess
├── .npmignore
├── .eslintignore
├── .gitignore
├── doc
└── viewer_screenshot.png
├── src
├── styles
│ ├── nodes.scss
│ └── links.scss
├── main.js
├── utils
│ ├── mol_view_utils.js
│ └── molecule_utils.js
└── components
│ ├── nodes.jsx
│ ├── links.jsx
│ └── molecule_2d.jsx
├── .babelrc
├── test
├── e2e
│ ├── fixtures
│ │ └── setup.js
│ └── specs
│ │ └── selection_spec.js
└── unit
│ └── utils
│ ├── mol_view_utils_spec.js
│ └── molecule_utils_spec.js
├── .eslintrc
├── scripts
└── download_selenium.js
├── .travis.yml
├── webpack.config.js
├── nightwatch.conf.js
├── package.json
├── karma.conf.js
├── README.md
└── LICENSE
/example/img/.gitignore:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | src/
2 | example/
3 |
--------------------------------------------------------------------------------
/example/.gitattributes:
--------------------------------------------------------------------------------
1 | * text=auto
2 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | example/js/residue.js
2 | example/js/bipyridine.js
3 |
--------------------------------------------------------------------------------
/example/tile.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Autodesk/molecule-2d-for-react/HEAD/example/tile.png
--------------------------------------------------------------------------------
/example/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Autodesk/molecule-2d-for-react/HEAD/example/favicon.ico
--------------------------------------------------------------------------------
/example/tile-wide.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Autodesk/molecule-2d-for-react/HEAD/example/tile-wide.png
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | dist/
2 | node_modules/
3 | npm-debug.log
4 | selenium-debug.log
5 | reports/
6 | screenshots/
7 |
--------------------------------------------------------------------------------
/example/css/home.css:
--------------------------------------------------------------------------------
1 | .container {
2 | display: flex;
3 | }
4 |
5 | .data {
6 | margin-left: 4rem;
7 | }
8 |
--------------------------------------------------------------------------------
/doc/viewer_screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Autodesk/molecule-2d-for-react/HEAD/doc/viewer_screenshot.png
--------------------------------------------------------------------------------
/example/robots.txt:
--------------------------------------------------------------------------------
1 | # www.robotstxt.org/
2 |
3 | # Allow crawling of all content
4 | User-agent: *
5 | Disallow:
6 |
--------------------------------------------------------------------------------
/example/apple-touch-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Autodesk/molecule-2d-for-react/HEAD/example/apple-touch-icon.png
--------------------------------------------------------------------------------
/src/styles/nodes.scss:
--------------------------------------------------------------------------------
1 | .node {
2 | text {
3 | font: 10px sans-serif;
4 | pointer-events: none;
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | "es2015",
4 | "es2016",
5 | "es2017",
6 | "stage-2",
7 | "react"
8 | ]
9 | }
10 |
11 |
--------------------------------------------------------------------------------
/test/e2e/fixtures/setup.js:
--------------------------------------------------------------------------------
1 | module.exports = function setup(browser) {
2 | browser.windowSize('current', 1700, 1100);
3 |
4 | return browser;
5 | };
6 |
--------------------------------------------------------------------------------
/example/.gitignore:
--------------------------------------------------------------------------------
1 | # Include your project-specific ignores in this file
2 | # Read about how to use .gitignore: https://help.github.com/articles/ignoring-files
3 |
--------------------------------------------------------------------------------
/src/styles/links.scss:
--------------------------------------------------------------------------------
1 | .link {
2 | line {
3 | stroke: #696969;
4 |
5 | &.separator {
6 | stroke: #fff;
7 | stroke-width: 2px;
8 | }
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/example/.editorconfig:
--------------------------------------------------------------------------------
1 | # editorconfig.org
2 |
3 | root = true
4 |
5 | [*]
6 | charset = utf-8
7 | indent_size = 4
8 | indent_style = space
9 | insert_final_newline = true
10 | trim_trailing_whitespace = true
11 |
12 | [*.md]
13 | trim_trailing_whitespace = false
14 |
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "airbnb",
3 | "rules": {
4 | "react/no-unused-prop-types": 1,
5 | // These rules are violated by normal d3 usage
6 | "no-underscore-dangle": 1,
7 | "no-param-reassign": 1,
8 | "react/jsx-no-bind": 1,
9 | },
10 | "parser": "babel-eslint",
11 | "globals": {
12 | "window": true,
13 | "document": true,
14 | },
15 | }
16 |
--------------------------------------------------------------------------------
/example/browserconfig.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/example/crossdomain.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
15 |
16 |
--------------------------------------------------------------------------------
/scripts/download_selenium.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs');
2 | const seleniumDownload = require('selenium-download');
3 |
4 | const BINPATH = './node_modules/nightwatch/bin/';
5 |
6 | /**
7 | * selenium-download does exactly what it's name suggests;
8 | * downloads (or updates) the version of Selenium (& chromedriver)
9 | * on your localhost where it will be used by Nightwatch.
10 | */
11 | fs.stat(`${BINPATH}selenium.jar`, (err, stat) => {
12 | if (err || !stat || stat.size < 1) {
13 | seleniumDownload.ensure(BINPATH, (error) => {
14 | if (error) throw new Error(error);
15 | console.log('✔ Selenium & Chromedriver downloaded to:', BINPATH);
16 | });
17 | }
18 | });
19 |
20 |
--------------------------------------------------------------------------------
/src/main.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2016 Autodesk Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | import Molecule2d from './components/molecule_2d.jsx';
18 |
19 | export default Molecule2d;
20 |
--------------------------------------------------------------------------------
/example/js/plugins.js:
--------------------------------------------------------------------------------
1 | // Avoid `console` errors in browsers that lack a console.
2 | (function() {
3 | var method;
4 | var noop = function () {};
5 | var methods = [
6 | 'assert', 'clear', 'count', 'debug', 'dir', 'dirxml', 'error',
7 | 'exception', 'group', 'groupCollapsed', 'groupEnd', 'info', 'log',
8 | 'markTimeline', 'profile', 'profileEnd', 'table', 'time', 'timeEnd',
9 | 'timeline', 'timelineEnd', 'timeStamp', 'trace', 'warn'
10 | ];
11 | var length = methods.length;
12 | var console = (window.console = window.console || {});
13 |
14 | while (length--) {
15 | method = methods[length];
16 |
17 | // Only stub undefined methods.
18 | if (!console[method]) {
19 | console[method] = noop;
20 | }
21 | }
22 | }());
23 |
24 | // Place any jQuery/helper plugins in here.
25 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - 6.4.0
4 | script:
5 | - npm test
6 | - npm run setup-selenium
7 | - npm run example &
8 | - npm run e2e
9 | deploy:
10 | provider: npm
11 | email: justinjmccandless@gmail.com
12 | api_key:
13 | secure: LCfY4dl0AI8fwb77PvjnL7LKzKGIkVFG2BbMxEipHHqH4X91xLSEvAYfafQsyYBspVc/erkupveOUC77aQe6FxdzSOiNlUD1x9hVFS0eQStZJpmAPj7YctSXWx5956ZW74aHSgVLdIjC68cl6FCAiymxKC5tRmPbNcUMOqLxMK1Tj+dGO83VIYz3nQxz9FuLMR6nCPTBUVu/+re9xlqmMqRj/b7S2GsuDxq00ReLl/5SHf4/o/ftmiOxHI8mFuayuJ4BnI/9JEytTRiUA40IkB8LRz8s/OnlVIcwSu7iGPKoeNiS+Mc+XWyIoKIGYCBRCnarZPelPlZk5CpUoH/S9G7H3hHeTiNLaVJpCrNKZ+hcpNqEifPOhegHAn1AqLVPqphfYG6Dt9uXAvPABDVrKqaVFAbQDZdfobIayfpxAm3pLl2ZEuKTSZfzzzY+yg75IAGE912d5dgGrkbqvZMVCVrhNbEBpKrHJrvb/MpsqvfhMwxgPPJu/xbWnTUhd7h8yyMXklNLoeDEHy4dcObQauxZmxEiqDaao+0AqlVQF6gSVtyfJxTAnBvgYJLQcACb3EBTQkjaPYuSDHt9AE6HsTRTzQLRbbTCzDW2F5i/EGdRD6uLHZUQDDnAMYXTrRYufIgfUdR3PkPMQFcX6kIGOFHuosxBGVFHSJKGkOtrJ8M=
14 | on:
15 | tags: true
16 | repo: Autodesk/molecule-2d-for-react
17 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | const InlineEnviromentVariablesPlugin = require('inline-environment-variables-webpack-plugin');
2 | const path = require('path');
3 |
4 | module.exports = {
5 | entry: {
6 | app: ['./src/main.js'],
7 | },
8 | output: {
9 | path: path.resolve(__dirname, 'dist'),
10 | publicPath: '/js/',
11 | filename: 'bundle.js',
12 | library: true,
13 | libraryTarget: 'commonjs2',
14 | },
15 | externals: [
16 | /^[a-z\-0-9]+$/,
17 | ],
18 | module: {
19 | loaders: [
20 | {
21 | test: /\.jsx?$/,
22 | exclude: /(node_modules|bower_components)/,
23 | loader: 'babel',
24 | }, {
25 | test: /\.jsx?$/,
26 | exclude: /(node_modules|bower_components)/,
27 | loader: 'eslint-loader',
28 | }, {
29 | test: /\.scss$/,
30 | loaders: ['style', 'css', 'sass'],
31 | },
32 | ],
33 | },
34 | devtool: 'source-map',
35 | plugins: [
36 | new InlineEnviromentVariablesPlugin(),
37 | ],
38 | };
39 |
--------------------------------------------------------------------------------
/example/webpack.config.js:
--------------------------------------------------------------------------------
1 | const InlineEnviromentVariablesPlugin = require('inline-environment-variables-webpack-plugin');
2 | const path = require('path');
3 |
4 | module.exports = {
5 | entry: {
6 | example: ['babel-polyfill', './example/js/main.jsx'],
7 | },
8 | output: {
9 | path: path.resolve(__dirname, 'dist'),
10 | publicPath: '/js/',
11 | filename: 'bundle.[name].js',
12 | },
13 | devServer: {
14 | port: '4000',
15 | hot: true,
16 | },
17 | module: {
18 | loaders: [
19 | {
20 | test: /\.jsx?$/,
21 | exclude: /(node_modules|bower_components)/,
22 | loader: 'babel',
23 | }, {
24 | test: /\.jsx?$/,
25 | exclude: /(node_modules|bower_components)/,
26 | loader: 'eslint-loader',
27 | }, {
28 | test: /\.scss$/,
29 | loaders: ['style', 'css', 'sass'],
30 | },
31 | ],
32 | },
33 | devtool: 'source-map',
34 | plugins: [
35 | new InlineEnviromentVariablesPlugin(),
36 | ],
37 | };
38 |
--------------------------------------------------------------------------------
/test/e2e/specs/selection_spec.js:
--------------------------------------------------------------------------------
1 | const setup = require('../fixtures/setup');
2 |
3 | module.exports = {
4 | 'Selection Spec': (browser) => {
5 | setup(browser)
6 | .url(browser.launchUrl)
7 | .waitForElementVisible('.molecule-2d svg', 1000, 'molecule-2d SVG element appears')
8 | .waitForElementVisible('g.node', 1000, 'Nodes are rendered in the SVG.')
9 | .click('g.node')
10 | .assert.elementPresent('g.node.selected', 'Clicking a node adds a selected class to it')
11 | .click('g.node:last-child')
12 | // Clicking a different node selects both of them
13 | .elements('css selector', 'g.node.selected', (result) => {
14 | browser.expect(result.value.length).to.equal(2);
15 | })
16 | .click('g.node')
17 | // Clicking a selected node unselects it
18 | .elements('css selector', 'g.node.selected', (result) => {
19 | browser.expect(result.value.length).to.equal(1);
20 | })
21 | .click('g.node:last-child')
22 | .assert.elementNotPresent('g.node.selected', 'Clicking all selected nodes unselects them all')
23 | .end();
24 | },
25 | };
26 |
--------------------------------------------------------------------------------
/nightwatch.conf.js:
--------------------------------------------------------------------------------
1 | const BINPATH = './node_modules/nightwatch/bin/';
2 |
3 | module.exports = {
4 | src_folders: [
5 | 'test/e2e', // Where you are storing your Nightwatch e2e/UAT tests
6 | ],
7 | output_folder: './reports', // reports (test outcome) output by nightwatch
8 | selenium: { // downloaded by selenium-download module (see readme)
9 | start_process: true, // tells nightwatch to start/stop the selenium process
10 | server_path: `${BINPATH}selenium.jar`,
11 | log_path: '',
12 | host: '127.0.0.1',
13 | port: 4444, // standard selenium port
14 | },
15 | test_settings: {
16 | default: {
17 | screenshots: {
18 | enabled: false,
19 | path: './screenshots',
20 | },
21 | globals: {
22 | waitForConditionTimeout: 5000, // sometimes internet is slow so wait.
23 | },
24 | launch_url: 'http://localhost:4000',
25 | desiredCapabilities: {
26 | browserName: 'phantomjs',
27 | javascriptEnabled: true,
28 | acceptSslCerts: true,
29 | 'phantomjs.binary.path': './node_modules/.bin/phantomjs',
30 | 'phantomjs.cli.args': [],
31 | },
32 | },
33 | },
34 | };
35 |
--------------------------------------------------------------------------------
/example/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
21 |
22 |
23 |
24 |
molecule-2d
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/example/js/main.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { render } from 'react-dom';
3 | import ExampleSettings from './example_settings.jsx';
4 | import Molecule2d from '../../src/main.js';
5 | import bipyridine from './bipyridine';
6 | import residue from './residue';
7 |
8 | class Example extends React.Component {
9 | constructor(props) {
10 | super(props);
11 |
12 | this.state = {
13 | selectedAtomIds: [],
14 | modelData: residue,
15 | };
16 | }
17 |
18 | onChangeSelection = (selectedAtomIds) => {
19 | this.setState({
20 | selectedAtomIds,
21 | });
22 | }
23 |
24 | onToggleMolecule = () => {
25 | this.setState({
26 | modelData: this.state.modelData === bipyridine ? residue : bipyridine,
27 | });
28 | }
29 |
30 | render() {
31 | return (
32 |
33 |
38 |
43 |
44 | );
45 | }
46 | }
47 |
48 | render(
49 | ,
50 | document.querySelector('.molecule-2d')
51 | );
52 |
--------------------------------------------------------------------------------
/example/js/residue.js:
--------------------------------------------------------------------------------
1 | const residue = {
2 | nodes: [
3 | {"id":1000,"atom":"HE22"},
4 | {"id":991,"atom":"C"},
5 | {"id":997,"atom":"NE2"},
6 | {"id":990,"atom":"CA"},
7 | {"id":995,"atom":"CD"},
8 | {"id":996,"atom":"OE1"},
9 | {"id":993,"atom":"CB"},
10 | {"id":992,"atom":"O"},
11 | {"id":994,"atom":"CG"},
12 | {"id":999,"atom":"HE21"},
13 | {"id":989,"atom":"N"},
14 | {"id":998,"atom":"H"}
15 | ],
16 | links: [
17 | { id: 90, "source":989,"target":990,"bond":1, strength: 1, distance: 30.0 },
18 | { id: 91, "source":989,"target":998,"bond":1, strength: 1, distance: 30.0 },
19 | { id: 92, "source":990,"target":991,"bond":1, strength: 1, distance: 30.0 },
20 | { id: 93, "source":990,"target":993,"bond":1, strength: 1, distance: 30.0 },
21 | { id: 94, "source":991,"target":992,"bond":2, strength: 1, distance: 30.0 },
22 | { id: 95, "source":993,"target":994,"bond":1, strength: 1, distance: 30.0 },
23 | { id: 96, "source":994,"target":995,"bond":1, strength: 1, distance: 30.0 },
24 | { id: 97, "source":995,"target":997,"bond":1, strength: 1, distance: 30.0 },
25 | { id: 98, "source":995,"target":996,"bond":2, strength: 1, distance: 30.0 },
26 | { id: 99, "source":997,"target":1000,"bond":1, strength: 1, distance: 30.0 },
27 | { id: 100, "source":997,"target":999,"bond":1, strength: 1, distance: 30.0 }
28 | ],
29 | };
30 |
31 | export default residue;
32 |
--------------------------------------------------------------------------------
/example/js/example_settings.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | class ExampleSettings extends React.Component {
4 | constructor(props) {
5 | super(props);
6 |
7 | this.state = {
8 | selectedAtomIds: '',
9 | };
10 | }
11 |
12 | componentWillReceiveProps(nextProps) {
13 | this.setState({
14 | selectedAtomIds: JSON.stringify(nextProps.selectedAtomIds),
15 | });
16 | }
17 |
18 | onChangeSelection = (event) => {
19 | this.setState({
20 | selectedAtomIds: event.target.value,
21 | });
22 | }
23 |
24 | onBlurSelection = (event) => {
25 | let selectedAtomIds;
26 |
27 | try {
28 | selectedAtomIds = JSON.parse(event.target.value);
29 | } catch (err) {
30 | throw err;
31 | }
32 |
33 | this.props.onChangeSelection(selectedAtomIds);
34 | }
35 |
36 | render() {
37 | return (
38 |
39 |
42 |
selectedAtomIds
43 |
48 |
49 | );
50 | }
51 | }
52 |
53 | ExampleSettings.propTypes = {
54 | selectedAtomIds: React.PropTypes.arrayOf(React.PropTypes.number),
55 | onChangeSelection: React.PropTypes.func,
56 | onToggleMolecule: React.PropTypes.func,
57 | };
58 |
59 | export default ExampleSettings;
60 |
--------------------------------------------------------------------------------
/src/utils/mol_view_utils.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2016 Autodesk Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | const molViewUtils = {
18 | getBondWidth(d) {
19 | if (!d || isNaN(parseInt(d.bond, 10))) {
20 | throw new Error('Invalid input');
21 | }
22 | if (d.bond < 0) {
23 | throw new Error('d.bond must be at least 0');
24 | }
25 |
26 | return `${(d.bond * 4) - 2}px`;
27 | },
28 |
29 | chooseColor(d, defaultValue) {
30 | const color = d.category || d.color;
31 | if (color) {
32 | return color;
33 | }
34 | return defaultValue;
35 | },
36 |
37 | withDefault(test, defaultValue) {
38 | if (typeof test === 'undefined') {
39 | return defaultValue;
40 | }
41 |
42 | return test;
43 | },
44 |
45 | /*
46 | bondClickCallback(mywidget) { // not hooked up yet
47 | mywidget.model.set('clicked_bond_indices',
48 | [this.attributes.sourceIndex.value*1,
49 | this.attributes.targetIndex.value*1]);
50 | mywidget.model.save();
51 | },
52 | */
53 | };
54 |
55 | export default molViewUtils;
56 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "molecule-2d-for-react",
3 | "version": "0.2.3",
4 | "description": "2D molecule visualization using React and D3",
5 | "main": "dist/bundle.js",
6 | "keywords": [
7 | "molecule",
8 | "chemical",
9 | "react",
10 | "visualization",
11 | "d3",
12 | "2d"
13 | ],
14 | "dependencies": {
15 | "d3": "^4.1.1",
16 | "react": "^15.3.2",
17 | "react-dom": "^15.3.2"
18 | },
19 | "devDependencies": {
20 | "babel-core": "^6.7.7",
21 | "babel-eslint": "^7.0.0",
22 | "babel-loader": "^6.2.4",
23 | "babel-polyfill": "^6.16.0",
24 | "babel-preset-es2015": "^6.16.0",
25 | "babel-preset-es2016": "^6.16.0",
26 | "babel-preset-es2017": "^6.16.0",
27 | "babel-preset-react": "^6.11.1",
28 | "babel-preset-stage-2": "^6.17.0",
29 | "chai": "^3.5.0",
30 | "css-loader": "^0.25.0",
31 | "eslint": "^3.5.0",
32 | "eslint-config-airbnb": "^11.1.0",
33 | "eslint-loader": "^1.3.0",
34 | "eslint-plugin-import": "^1.15.0",
35 | "eslint-plugin-jsx-a11y": "^2.2.2",
36 | "eslint-plugin-react": "^6.4.1",
37 | "inline-environment-variables-webpack-plugin": "^1.1.0",
38 | "karma": "^0.13.22",
39 | "karma-mocha": "^0.2.2",
40 | "karma-phantomjs-launcher": "^1.0.0",
41 | "karma-webpack": "^1.7.0",
42 | "mocha": "^2.4.5",
43 | "nightwatch": "^0.9.8",
44 | "node-sass": "^3.9.3",
45 | "phantomjs-prebuilt": "^2.1.7",
46 | "sass-loader": "^4.0.2",
47 | "selenium-download": "^2.0.6",
48 | "sinon": "^2.0.0-pre.2",
49 | "style-loader": "^0.13.1",
50 | "webpack": "^1.13.2",
51 | "webpack-dev-server": "^1.14.1"
52 | },
53 | "scripts": {
54 | "test": "karma start --single-run",
55 | "tdd": "karma start",
56 | "setup-selenium": "node scripts/download_selenium.js",
57 | "e2e": "./node_modules/nightwatch/bin/nightwatch",
58 | "build": "webpack -p --config webpack.config.js",
59 | "watch": "webpack --config webpack.config.js --watch",
60 | "example": "NODE_ENV=DEVELOPMENT webpack-dev-server --content-base example/ --config example/webpack.config.js --progress --colors",
61 | "prepublish": "npm run build"
62 | },
63 | "author": "Autodesk Bio/Nano",
64 | "license": "Apache-2.0",
65 | "repository": {
66 | "type": "git",
67 | "url": "https://github.com/Autodesk/molecule-2d-for-react.git"
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/karma.conf.js:
--------------------------------------------------------------------------------
1 | // Karma configuration
2 | // Generated on Sat Apr 30 2016 13:33:59 GMT-0700 (PDT)
3 |
4 | module.exports = function(config) {
5 | config.set({
6 |
7 | // base path that will be used to resolve all patterns (eg. files, exclude)
8 | basePath: '',
9 |
10 |
11 | // frameworks to use
12 | // available frameworks: https://npmjs.org/browse/keyword/karma-adapter
13 | frameworks: ['mocha'],
14 |
15 |
16 | // list of files / patterns to load in the browser
17 | files: [
18 | 'test/unit/**/*_spec.js'
19 | ],
20 |
21 |
22 | // list of files to exclude
23 | exclude: [
24 | ],
25 |
26 |
27 | // preprocess matching files before serving them to the browser
28 | // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor
29 | preprocessors: {
30 | 'test/unit/*_spec.js': ['webpack'],
31 | 'test/unit/**/*_spec.js': ['webpack']
32 | },
33 |
34 |
35 | webpack: {
36 | module: {
37 | loaders: [{
38 | test: /\.(js|jsx)$/, exclude: /(bower_components|node_modules)/,
39 | loader: 'babel-loader'
40 | }, {
41 | test: /\.scss$/,
42 | include: /example\/css/,
43 | loaders: ['style', 'css', 'sass'],
44 | }]
45 | }
46 | },
47 |
48 |
49 | // test results reporter to use
50 | // possible values: 'dots', 'progress'
51 | // available reporters: https://npmjs.org/browse/keyword/karma-reporter
52 | reporters: ['progress'],
53 |
54 |
55 | // web server port
56 | port: 9876,
57 |
58 |
59 | // enable / disable colors in the output (reporters and logs)
60 | colors: true,
61 |
62 |
63 | // level of logging
64 | // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
65 | logLevel: config.LOG_INFO,
66 |
67 |
68 | // enable / disable watching file and executing tests whenever any file changes
69 | autoWatch: true,
70 |
71 |
72 | // start these browsers
73 | // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher
74 | browsers: ['PhantomJS'],
75 |
76 |
77 | // Continuous Integration mode
78 | // if true, Karma captures browsers, runs the tests and exits
79 | singleRun: false,
80 |
81 | // Concurrency level
82 | // how many browser should be started simultaneous
83 | concurrency: Infinity
84 | })
85 | }
86 |
--------------------------------------------------------------------------------
/test/unit/utils/mol_view_utils_spec.js:
--------------------------------------------------------------------------------
1 | /* global describe, it, before, after, beforeEach, afterEach */
2 |
3 | import { assert, expect } from 'chai';
4 | import sinon from 'sinon';
5 | import d3 from 'd3';
6 | import molViewUtils from '../../../src/utils/mol_view_utils';
7 |
8 | describe('molViewUtils', () => {
9 | describe('getBondWidth', () => {
10 | describe('when given invalid input', () => {
11 | it('throws an error', () => {
12 | assert.throws(molViewUtils.getBondWidth.bind(null, 'words'));
13 | assert.throws(molViewUtils.getBondWidth.bind(null, {}));
14 | assert.throws(molViewUtils.getBondWidth.bind(null, { bond: -1 }));
15 | });
16 | });
17 |
18 | describe('when given an object with a bond number', () => {
19 | it('returns a pixel string', () => {
20 | assert.equal(molViewUtils.getBondWidth({ bond: 4 }), '14px');
21 | });
22 | });
23 | });
24 |
25 | describe('chooseColor', () => {
26 | const color = '#abcdef';
27 | let d = {};
28 |
29 | beforeEach(() => {
30 | d = {};
31 | });
32 |
33 | describe('when d has a category', () => {
34 | beforeEach(() => {
35 | d.category = color;
36 | });
37 |
38 | it('chooses the corresponding color from d3\'s color palette', () => {
39 | expect(molViewUtils.chooseColor(d)).to.equal(color);
40 | });
41 | });
42 |
43 | describe('when d has a color and not a category', () => {
44 | beforeEach(() => {
45 | d.color = color;
46 | });
47 |
48 | it('returns d.color', () => {
49 | expect(molViewUtils.chooseColor(d)).to.equal(color);
50 | });
51 | });
52 |
53 | describe('when d has no color and no category', () => {
54 | it('returns defaultValue', () => {
55 | expect(molViewUtils.chooseColor(d, color)).to.equal(color);
56 | });
57 | });
58 | });
59 |
60 | describe('withDefault', () => {
61 | const defaultValue = 'imadefaultValue';
62 | let test;
63 |
64 | describe('when given an undefined test value', () => {
65 | before(() => {
66 | test = undefined;
67 | });
68 |
69 | it('returns defaultValue', () => {
70 | assert.equal(molViewUtils.withDefault(undefined, defaultValue), defaultValue);
71 | });
72 | });
73 |
74 | describe('when give a test value that\'s not undefined', () => {
75 | before(() => {
76 | test = 'imatestval';
77 | });
78 |
79 | it('returns test', () => {
80 | assert.equal(molViewUtils.withDefault(test, defaultValue), test);
81 | });
82 | });
83 | });
84 | });
85 |
--------------------------------------------------------------------------------
/src/components/nodes.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {
3 | drag,
4 | event as d3Event,
5 | scaleSqrt,
6 | select,
7 | selectAll,
8 | } from 'd3';
9 | import molViewUtils from '../utils/mol_view_utils';
10 |
11 | require('../styles/nodes.scss');
12 |
13 | const SELECTED_COLOR = '#39f8ff';
14 |
15 | class Nodes extends React.Component {
16 | static onDragged(d) {
17 | d.fx = d3Event.x;
18 | d.fy = d3Event.y;
19 | }
20 |
21 | componentDidMount() {
22 | this.renderD3();
23 | }
24 |
25 | componentDidUpdate() {
26 | this.renderD3();
27 | }
28 |
29 | renderD3() {
30 | if (!this.nodesContainer) {
31 | return;
32 | }
33 |
34 | const container = select(this.nodesContainer);
35 |
36 | const nodes = container.selectAll('.node')
37 | .data(this.props.nodes, d => d.id);
38 |
39 | const newNodesG = nodes.enter().append('g');
40 |
41 | newNodesG
42 | .attr('class', 'node')
43 | .on('click', this.props.onClickNode)
44 | .attr('index', d => d.id)
45 | .call(drag()
46 | .on('start', this.props.onDragStartedNode)
47 | .on('drag', Nodes.onDragged)
48 | .on('end', this.props.onDragEndedNode)
49 | );
50 |
51 | container.selectAll('.node')
52 | .classed('selected', d =>
53 | (this.props.selectedAtomIds.indexOf(d.id) !== -1 ? SELECTED_COLOR : '')
54 | );
55 |
56 | const radius = scaleSqrt().range([0, 6]);
57 |
58 | // circle for each atom (background color white by default)
59 | newNodesG.append('circle')
60 | .attr('class', 'atom-circle')
61 | .attr('r', d => radius(molViewUtils.withDefault(d.size, 1.5)))
62 | .style('fill', d => molViewUtils.chooseColor(d, 'white'));
63 | selectAll('.atom-circle')
64 | .style('stroke', d =>
65 | (this.props.selectedAtomIds.indexOf(d.id) !== -1 ? SELECTED_COLOR : '')
66 | );
67 |
68 | // atom labels
69 | newNodesG.append('text')
70 | .attr('class', 'atom-label')
71 | .attr('dy', '.35em')
72 | .attr('text-anchor', 'middle')
73 | .text(d => d.atom);
74 | container.selectAll('.atom-label')
75 | .attr('fill', (d) => {
76 | const color = this.props.selectedAtomIds.indexOf(d.id) !== -1 ?
77 | SELECTED_COLOR : '';
78 | return molViewUtils.withDefault(d.textcolor, color);
79 | });
80 |
81 | nodes.exit()
82 | .remove();
83 | }
84 |
85 | render() {
86 | return (
87 | { this.nodesContainer = c; }} />
88 | );
89 | }
90 | }
91 |
92 | Nodes.propTypes = {
93 | selectedAtomIds: React.PropTypes.arrayOf(React.PropTypes.number),
94 | nodes: React.PropTypes.arrayOf(React.PropTypes.object),
95 | onClickNode: React.PropTypes.func,
96 | onDragStartedNode: React.PropTypes.func,
97 | onDragEndedNode: React.PropTypes.func,
98 | };
99 |
100 | export default Nodes;
101 |
--------------------------------------------------------------------------------
/src/components/links.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {
3 | select,
4 | } from 'd3';
5 | import molViewUtils from '../utils/mol_view_utils';
6 |
7 | require('../styles/links.scss');
8 |
9 | class Links extends React.Component {
10 | componentDidMount() {
11 | this.renderD3();
12 | }
13 |
14 | componentDidUpdate() {
15 | this.renderD3();
16 | }
17 |
18 | renderD3() {
19 | if (!this.linksContainer) {
20 | return;
21 | }
22 |
23 | const container = select(this.linksContainer);
24 |
25 | const links = container
26 | .selectAll('.link')
27 | .data(this.props.links, d => d.id);
28 |
29 | const newLinksG = links.enter()
30 | .append('g')
31 | .attr('class', 'link');
32 |
33 | // all edges (includes both bonds and distance constraints)
34 | newLinksG
35 | .append('line')
36 | .attr('class', 'link-line');
37 | container.selectAll('.link-line')
38 | .attr('source', d =>
39 | (typeof d.source.id !== 'undefined' ? d.source.id : d.source)
40 | )
41 | .attr('target', d => (typeof d.target.id !== 'undefined' ? d.target.id : d.target))
42 | .style('stroke-width', molViewUtils.getBondWidth)
43 | .style('stroke-dasharray', (d) => {
44 | if (d.style === 'dashed') {
45 | return 5;
46 | }
47 |
48 | return 0;
49 | })
50 | .style('stroke', d => molViewUtils.chooseColor(d, 'black'))
51 | .style('opacity', (d) => {
52 | if (d.bond !== 0) {
53 | return undefined;
54 | }
55 | return 0.0;
56 | });
57 |
58 | // text placeholders for all edges
59 | newLinksG
60 | .append('text');
61 | container.selectAll('.link').selectAll('text')
62 | .attr('x', d => d.source.x || 0)
63 | .attr('y', d => d.source.y || 0)
64 | .attr('text-anchor', 'middle')
65 | .text(() => ' ');
66 |
67 | // double and triple bonds
68 | newLinksG
69 | .filter(d => d.bond > 1)
70 | .append('line')
71 | .attr('class', 'separator separator-double');
72 | container.selectAll('.separator-double')
73 | .style('stroke', '#FFF')
74 | .style('stroke-width', d => `${(d.bond * 4) - 5}px`);
75 |
76 | // triple bonds
77 | newLinksG
78 | .filter(d => d.bond === 3)
79 | .append('line')
80 | .attr('class', 'separator separator-triple');
81 | container.selectAll('.separator-triple')
82 | .style('stroke', d => molViewUtils.chooseColor(d, 'black'))
83 | .style('stroke-width', () => molViewUtils.getBondWidth(1));
84 |
85 | links.exit()
86 | .remove();
87 | }
88 |
89 | render() {
90 | return (
91 | { this.linksContainer = c; }} />
92 | );
93 | }
94 | }
95 |
96 | Links.propTypes = {
97 | links: React.PropTypes.arrayOf(React.PropTypes.object),
98 | };
99 |
100 | export default Links;
101 |
--------------------------------------------------------------------------------
/src/utils/molecule_utils.js:
--------------------------------------------------------------------------------
1 | const moleculeUtils = {
2 | /**
3 | * Given two arrays of ids, return true if they contain the same values in any order,
4 | * ignoring duplicates
5 | * @param idsA {Array}
6 | * @param idsB {Array}
7 | * @returns {Boolean}
8 | */
9 | compareIds(idsA, idsB) {
10 | // If one array is empty and the other isn't, they can't contain the same values
11 | if ((!idsA.length && idsB.length) || (idsA.length && !idsB.length)) {
12 | return false;
13 | }
14 |
15 | const mapA = new Map();
16 | idsA.forEach((id) => {
17 | mapA.set(id, false);
18 | });
19 |
20 | for (const id of idsB) {
21 | // If an id exists in B but not in A, not equivalent
22 | if (!mapA.has(id)) {
23 | return false;
24 | }
25 | mapA.set(id, true);
26 | }
27 |
28 | // If an id exists in A but not B, not equivalent
29 | for (const tuple of mapA) {
30 | const value = tuple[1];
31 | if (!value) {
32 | return false;
33 | }
34 | }
35 |
36 | return true;
37 | },
38 |
39 | /**
40 | * Due to craziness of D3, we need to keep our main modelData state as the same object and mutate
41 | * it in place. The same goes for all sub-objects within modelData.
42 | * @param oldModelData {Object}
43 | * @param newModelData {Object}
44 | * @returns {Object}
45 | */
46 | updateObjectInPlace(oldObject, newObject) {
47 | Object.keys(newObject).forEach((key) => {
48 | if (oldObject[key] instanceof Object && newObject[key] instanceof Object) {
49 | oldObject[key] = moleculeUtils.updateObjectInPlace(oldObject[key], newObject[key]);
50 | } else {
51 | oldObject[key] = newObject[key];
52 | }
53 | });
54 |
55 | return oldObject;
56 | },
57 |
58 | /**
59 | * Given old and new arrays of models, update the old array's models in place based on id
60 | * If model ids don't perfectly match, just return newArray
61 | * O(n^2) :(
62 | * @param oldArray {Array}
63 | * @param newArray {Array}
64 | * @returns {Array}
65 | */
66 | updateModels(oldArray, newArray) {
67 | const sameIds = moleculeUtils.compareIds(
68 | oldArray.map(model => model.id), newArray.map(model => model.id)
69 | );
70 | if (!sameIds) {
71 | return newArray;
72 | }
73 |
74 | // Add or update everything in newArray to oldArray
75 | newArray.forEach((newModel) => {
76 | let found = false;
77 | for (let i = 0; i < oldArray.length; i += 1) {
78 | if (oldArray[i].id === newModel.id) {
79 | oldArray[i] = moleculeUtils.updateObjectInPlace(oldArray[i], newModel);
80 | found = true;
81 | break;
82 | }
83 | }
84 |
85 | if (!found) {
86 | oldArray.push(newModel);
87 | }
88 | });
89 |
90 | // Remove els in oldArray that don't exist in newArray
91 | for (let i = 0; i < oldArray.length; i += 1) {
92 | const oldModel = oldArray[i];
93 | const newModel = newArray.find(newModelI => newModelI.id === oldModel.id);
94 |
95 | if (!newModel) {
96 | oldArray.splice(i, 1);
97 | i -= 1;
98 | }
99 | }
100 |
101 | return oldArray;
102 | },
103 | };
104 |
105 | export default moleculeUtils;
106 |
--------------------------------------------------------------------------------
/example/js/bipyridine.js:
--------------------------------------------------------------------------------
1 | const bipyridine = {
2 | nodes: [
3 | { id: 0, atom: 'N' },
4 | { id: 1, atom: 'C' },
5 | { id: 2, atom: 'C3' },
6 | { id: 3, atom: 'C4' },
7 | { id: 4, atom: 'C5' },
8 | { id: 5, atom: 'C6' },
9 | { id: 6, atom: 'C7' },
10 | { id: 7, atom: 'N8' },
11 | { id: 8, atom: 'C9' },
12 | { id: 9, atom: 'C10' },
13 | { id: 10, atom: 'C11' },
14 | { id: 11, atom: 'C12' },
15 | ],
16 | links: [
17 | { id: 0, source: 1, strength: 1, distance: 29.91570726558207, target: 0, bond: 2 },
18 | { id: 1, source: 2, strength: 1, distance: 30.870992090958136, target: 1, bond: 1 },
19 | { id: 2, source: 3, strength: 1, distance: 30.669623995738846, target: 2, bond: 2 },
20 | { id: 3, source: 4, strength: 1, distance: 30.320011541554535, target: 3, bond: 1 },
21 | { id: 4, source: 5, strength: 1, distance: 30.462333981492616, target: 4, bond: 2 },
22 | { id: 5, source: 5, strength: 1, distance: 29.942081226928764, target: 0, bond: 1 },
23 | { id: 6, source: 6, strength: 1, distance: 33.10311986323948, target: 1, bond: 1 },
24 | { id: 7, source: 7, strength: 1, distance: 29.914643005725473, target: 6, bond: 2 },
25 | { id: 8, source: 8, strength: 1, distance: 29.94415643961273, target: 7, bond: 1 },
26 | { id: 9, source: 9, strength: 1, distance: 30.46307691747502, target: 8, bond: 2 },
27 | { id: 10, source: 10, strength: 1, distance: 30.317225221975708, target: 9, bond: 1 },
28 | { id: 11, source: 11, strength: 1, distance: 30.670350548371626, target: 10, bond: 2 },
29 | { id: 12, source: 11, strength: 1, distance: 30.87016403843685, target: 6, bond: 1 },
30 | { id: 13, bond: 0, source: 0, strength: 0.66, target: 2, distance: 52.55554066661288 },
31 | { id: 14, bond: 0, source: 0, strength: 0.66, target: 3, distance: 61.44022025806874 },
32 | { id: 15, bond: 0, source: 0, strength: 0.66, target: 4, distance: 53.38188235384736 },
33 | { id: 16, bond: 0, source: 0, strength: 0.66, target: 6, distance: 54.29639315497853 },
34 | { id: 17, bond: 0, source: 0, strength: 0.66, target: 7, distance: 62.034165551895676 },
35 | { id: 18, bond: 0, source: 0, strength: 0.32917806598897, target: 8, distance: 91.85214425042018 },
36 | { id: 19, bond: 0, source: 0, strength: 0.4360951942958962, target: 11, distance: 82.46783744539444 },
37 | { id: 20, bond: 0, source: 1, strength: 0.66, target: 3, distance: 53.510594708711665 },
38 | { id: 21, bond: 0, source: 1, strength: 0.66, target: 4, distance: 61.24937170518568 },
39 | { id: 22, bond: 0, source: 1, strength: 0.66, target: 5, distance: 51.3799317808033 },
40 | { id: 23, bond: 0, source: 1, strength: 0.66, target: 7, distance: 54.30254153057663 },
41 | { id: 24, bond: 0, source: 1, strength: 0.4462654710317949, target: 8, distance: 81.63853562307449 },
42 | { id: 25, bond: 0, source: 1, strength: 0.30494498544674653, target: 9, distance: 94.18343738917156 },
43 | { id: 26, bond: 0, source: 1, strength: 0.41980042870488044, target: 10, distance: 83.81696527314742 },
44 | { id: 27, bond: 0, source: 1, strength: 0.66, target: 11, distance: 55.75563128115401 },
45 | { id: 28, bond: 0, source: 2, strength: 0.66, target: 4, distance: 52.576423819807296 },
46 | { id: 29, bond: 0, source: 2, strength: 0.66, target: 5, distance: 59.387845223075736 },
47 | { id: 30, bond: 0, source: 2, strength: 0.66, target: 6, distance: 55.76335213238171 },
48 | { id: 31, bond: 0, source: 2, strength: 0.4358650877208918, target: 7, distance: 82.48671198587078 },
49 | { id: 32, bond: 0, source: 2, strength: 0.29105396519486687, target: 10, distance: 95.56171467590984 },
50 | { id: 33, bond: 0, source: 2, strength: 0.66, target: 11, distance: 65.13393127333862 },
51 | { id: 34, bond: 0, source: 3, strength: 0.66, target: 5, distance: 52.01413064966097 },
52 | { id: 35, bond: 0, source: 3, strength: 0.41980270594235247, target: 6, distance: 83.81677491695801 },
53 | { id: 36, bond: 0, source: 3, strength: 0.29131452162971666, target: 11, distance: 95.53556305648699 },
54 | { id: 37, bond: 0, source: 4, strength: 0.305018202617337, target: 6, distance: 94.17625685362525 },
55 | { id: 38, bond: 0, source: 5, strength: 0.4463947690054851, target: 6, distance: 81.62805361883866 },
56 | { id: 39, bond: 0, source: 5, strength: 0.32911474065283325, target: 7, distance: 91.85812235703492 },
57 | { id: 40, bond: 0, source: 6, strength: 0.66, target: 8, distance: 51.38270764449846 },
58 | { id: 41, bond: 0, source: 6, strength: 0.66, target: 9, distance: 61.24948063583885 },
59 | { id: 42, bond: 0, source: 6, strength: 0.66, target: 10, distance: 53.51103126085311 },
60 | { id: 43, bond: 0, source: 7, strength: 0.66, target: 9, distance: 53.381539040383615 },
61 | { id: 44, bond: 0, source: 7, strength: 0.66, target: 10, distance: 61.439202821325736 },
62 | { id: 45, bond: 0, source: 7, strength: 0.66, target: 11, distance: 52.554700448960794 },
63 | { id: 46, bond: 0, source: 8, strength: 0.66, target: 10, distance: 52.0157091536778 },
64 | { id: 47, bond: 0, source: 8, strength: 0.66, target: 11, distance: 59.39079652404067 },
65 | { id: 48, bond: 0, source: 9, strength: 0.66, target: 11, distance: 52.57455310699274 },
66 | ],
67 | };
68 |
69 | export default bipyridine;
70 |
--------------------------------------------------------------------------------
/src/components/molecule_2d.jsx:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2016 Autodesk Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | import React from 'react';
17 | import {
18 | event as d3Event,
19 | forceLink,
20 | forceManyBody,
21 | forceSimulation,
22 | forceCenter,
23 | select,
24 | } from 'd3';
25 | import Nodes from '../components/nodes.jsx';
26 | import Links from '../components/links.jsx';
27 | import moleculeUtils from '../utils/molecule_utils';
28 | import molViewUtils from '../utils/mol_view_utils';
29 |
30 | class Molecule2d extends React.Component {
31 | constructor(props) {
32 | super(props);
33 |
34 | this.state = {
35 | selectedAtomIds: props.selectedAtomIds || [],
36 | };
37 | }
38 |
39 | componentDidMount() {
40 | this.renderD3();
41 | }
42 |
43 | componentWillReceiveProps(nextProps) {
44 | if (!moleculeUtils.compareIds(this.state.selectedAtomIds, nextProps.selectedAtomIds)) {
45 | this.setState({
46 | selectedAtomIds: nextProps.selectedAtomIds,
47 | });
48 | }
49 | }
50 |
51 | componentDidUpdate() {
52 | this.renderD3();
53 | }
54 |
55 | onClickNode = (node) => {
56 | const selectedAtomIds = this.state.selectedAtomIds.slice(0);
57 | const index = selectedAtomIds.indexOf(node.id);
58 |
59 | if (index !== -1) {
60 | selectedAtomIds.splice(index, 1);
61 | } else {
62 | selectedAtomIds.push(node.id);
63 | }
64 |
65 | this.setState({
66 | selectedAtomIds,
67 | });
68 |
69 | this.props.onChangeSelection(selectedAtomIds);
70 | }
71 |
72 | onDragStartedNode = (d) => {
73 | if (!d3Event.active) {
74 | this.simulation.alphaTarget(0.3).restart();
75 | }
76 | d.fx = d.x;
77 | d.fy = d.y;
78 | }
79 |
80 | onDragEndedNode = (d) => {
81 | if (!d3Event.active) {
82 | this.simulation.alphaTarget(0);
83 | }
84 | d.fx = null;
85 | d.fy = null;
86 | }
87 |
88 | renderTransform = () => {
89 | if (!this.svg) {
90 | return;
91 | }
92 |
93 | const container = select(this.svg);
94 |
95 | // Nodes
96 | container.selectAll('.node')
97 | .attr('transform', d =>
98 | `translate(${d.x || 0},${d.y || 0})`
99 | );
100 |
101 | // Links
102 | const links = container.selectAll('.link');
103 |
104 | // keep edges pinned to their nodes
105 | links.selectAll('line')
106 | .attr('x1', d => d.source.x || 0)
107 | .attr('y1', d => d.source.y || 0)
108 | .attr('x2', d => d.target.x || 0)
109 | .attr('y2', d => d.target.y || 0);
110 |
111 | // keep edge labels pinned to the edges
112 | links.selectAll('text')
113 | .attr('x', d =>
114 | ((d.source.x || 0) + (d.target.x || 0)) / 2.0
115 | )
116 | .attr('y', d =>
117 | ((d.source.y || 0) + (d.target.y || 0)) / 2.0
118 | );
119 | }
120 |
121 | renderD3() {
122 | this.simulation = forceSimulation()
123 | .force('link', forceLink()
124 | .distance(d => molViewUtils.withDefault(d.distance, 20))
125 | .strength(d => molViewUtils.withDefault(d.strength, 1.0))
126 | )
127 | .force('charge', forceManyBody())
128 | .force('center', forceCenter(this.props.width / 2, this.props.height / 2));
129 |
130 | this.simulation
131 | .nodes(this.nodes)
132 | .on('tick', () => this.renderTransform());
133 |
134 | this.simulation.force('link')
135 | .id(d => d.id)
136 | .links(this.links);
137 | }
138 |
139 | render() {
140 | this.nodes = moleculeUtils.updateModels(this.nodes || [], this.props.modelData.nodes);
141 | this.links = moleculeUtils.updateModels(this.links || [], this.props.modelData.links);
142 |
143 | return (
144 |
160 | );
161 | }
162 | }
163 |
164 | Molecule2d.defaultProps = {
165 | width: 500.0,
166 | height: 500.0,
167 | selectedAtomIds: [],
168 | onChangeSelection: () => {},
169 | };
170 |
171 | Molecule2d.propTypes = {
172 | width: React.PropTypes.number,
173 | height: React.PropTypes.number,
174 | modelData: React.PropTypes.shape({
175 | nodes: React.PropTypes.array,
176 | links: React.PropTypes.array,
177 | }).isRequired,
178 | selectedAtomIds: React.PropTypes.arrayOf(React.PropTypes.number),
179 | onChangeSelection: React.PropTypes.func,
180 | };
181 |
182 | export default Molecule2d;
183 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Molecule2d
2 | [](https://travis-ci.org/Autodesk/molecule-2d-for-react)
3 |
4 | This project provides a React component that displays an interactive 2D representation of any molecule using D3.
5 |
6 |
7 |
8 | ## Installation
9 |
10 | npm install molecule-2d-for-react
11 |
12 | ## Usage
13 |
25 |
26 | See example/js/main.js for a working example.
27 |
28 | ## Props
29 | In order to set up your molecule visualization, just pass in the proper props data to the React component. Here are all of the parameters with explanations:
30 |
31 | ### modelData {Object} Required
32 | An object indicating the atoms an bonds to display. Of the form:
33 |
34 | {
35 | nodes: [
36 | { id: 0, atom: 'H' },
37 | ...
38 | ],
39 | links: [
40 | { id: 0, source: 0, target: 1, strength: 1, distance: 30.0, bond: 1 },
41 | ...
42 | ],
43 | }
44 |
45 | See example/js/bipyridine.js for an example of working modelData for a real molecule.
46 |
47 | ### selectedAtomIds {Array of Numbers} [[]]
48 | An array of atom ids to display as selected. This is deep copied into internal state and updated whenever the user clicks on an atom. See the `onChangeSelection` method below for how to listen to selection changes.
49 |
50 | ### width {Number} [500]
51 | The width of the SVG element.
52 |
53 | ### height {Number} [500]
54 | The height of the SVG element.
55 |
56 | ### onChangeSelection {Function}
57 | Called whenever selectedAtomIds is changed. Passed selectedAtomIds.
58 |
59 | ## Use in a Jupyter notebook
60 | It's also very easy to adapt this to work in a Jupyter Notebook as an [ipywidgets](https://github.com/ipython/ipywidgets) module, as it was made for the [Molecular Design Toolkit](https://github.com/Autodesk/molecular-design-toolkit) project. The [source code](https://github.com/Autodesk/notebook-molecular-visualization/blob/30e843393135d8b2d78ac055a6e366eb9c0ffde9/js/src/nbmolviz_2d_component.jsx) shows how this was done by wrapping this project in a Backbone view.
61 |
62 | ## What about 3d?
63 | Take a look at our sister project, [molecule-3d-for-react](https://github.com/Autodesk/molecule-3d-for-react), for a React component with a similar interface that renders a 3d visualization.
64 |
65 | ## Development
66 | A typical development flow might be to run the example while editing the code, where you'll want any changes to be immediately reflected in the example running in the browser. In that case you should run:
67 |
68 | npm run example
69 |
70 | ### Development within another project
71 | If you're using this in another project and want to make changes to this repository locally and see them reflected in your other project, first you'll need to do some setup. You can point your other project to use the local copy of Molecule2d like this:
72 |
73 | cd ~/path/to/molecule-2d-for-react
74 | npm link
75 | cd ~/path/to/other-project
76 | npm link molecule-2d-for-react
77 |
78 | See [this great blog post](http://justjs.com/posts/npm-link-developing-your-own-npm-modules-without-tears) for more info on `npm link`.
79 |
80 | Once you've linked your other project, you'll need to build Molecule2d (and likely your other project, too) every time you want your changes to reflect in your other project. You can do this manually with `npm run build`. If you want to rebuild Molecule2d automatically every time a change is made, run `npm run watch`.
81 |
82 | ### Running Tests
83 | Unit tests can be run with:
84 |
85 | npm test
86 |
87 | End-to-end tests can be run with:
88 |
89 | npm run e2e
90 |
91 | ### Releasing a new version
92 | Travis automatically publishes any new tagged commit to NPM. The best way to take advantage of this is to first create a new tagged commit using `npm version`:
93 |
94 | npm version patch -m "Upgrade to %s for reasons"
95 |
96 | Then push that commit to a new release branch, push the tag with `git push origin --tags` and open a pull request on Github. When you see that Travis has succeeded in deploying, merge it to master.
97 |
98 | ## License
99 |
100 | Copyright 2016 Autodesk Inc.
101 |
102 | Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at
103 |
104 | http://www.apache.org/licenses/LICENSE-2.0
105 |
106 | Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
107 |
108 |
109 | ## Contributing
110 |
111 | This project is developed and maintained by the [Molecular Design Toolkit](https://github.com/autodesk/molecular-design-toolkit) project. Please see that project's [CONTRIBUTING document](https://github.com/autodesk/molecular-design-toolkit/CONTRIBUTING.md) for details.
112 |
--------------------------------------------------------------------------------
/test/unit/utils/molecule_utils_spec.js:
--------------------------------------------------------------------------------
1 | /* global describe, it, before, after, beforeEach, afterEach */
2 |
3 | import { assert, expect } from 'chai';
4 | import 'babel-polyfill';
5 | import moleculeUtils from '../../../src/utils/molecule_utils';
6 |
7 | describe('moleculeUtils', () => {
8 | describe('compareIds', () => {
9 | let idsA;
10 | let idsB;
11 |
12 | describe('when given empty arrays', () => {
13 | beforeEach(() => {
14 | idsA = [];
15 | idsB = [];
16 | });
17 |
18 | it('returns true', () => {
19 | expect(moleculeUtils.compareIds(idsA, idsB)).to.equal(true);
20 | });
21 | });
22 |
23 | describe('when one array is empty and the other isn\'t', () => {
24 | beforeEach(() => {
25 | idsA = [1];
26 | idsB = [];
27 | });
28 |
29 | it('returns false', () => {
30 | expect(moleculeUtils.compareIds(idsA, idsB)).to.equal(false);
31 | });
32 | });
33 |
34 | describe('when arrays have totally different values', () => {
35 | beforeEach(() => {
36 | idsA = [1, 2, 3];
37 | idsB = [4, 5, 6];
38 | });
39 |
40 | it('returns false', () => {
41 | expect(moleculeUtils.compareIds(idsA, idsB)).to.equal(false);
42 | });
43 | });
44 |
45 | describe('when arrays have some (but not all) overlapping values', () => {
46 | beforeEach(() => {
47 | idsA = [1, 2, 3];
48 | idsB = [2, 3, 4];
49 | });
50 |
51 | it('returns false', () => {
52 | expect(moleculeUtils.compareIds(idsA, idsB)).to.equal(false);
53 | });
54 | });
55 |
56 | describe('when arrays have same exact values in order', () => {
57 | beforeEach(() => {
58 | idsA = [1, 2, 3];
59 | idsB = [1, 2, 3];
60 | });
61 |
62 | it('returns false', () => {
63 | expect(moleculeUtils.compareIds(idsA, idsB)).to.equal(true);
64 | });
65 | });
66 |
67 | describe('when arrays have same exact values in different order', () => {
68 | beforeEach(() => {
69 | idsA = [1, 2, 3];
70 | idsB = [2, 3, 1];
71 | });
72 |
73 | it('returns false', () => {
74 | expect(moleculeUtils.compareIds(idsA, idsB)).to.equal(true);
75 | });
76 | });
77 |
78 | describe('when arrays have same values with duplicates', () => {
79 | beforeEach(() => {
80 | idsA = [1, 2, 3];
81 | idsB = [2, 3, 1, 1, 1, 1, 2];
82 | });
83 |
84 | it('returns false', () => {
85 | expect(moleculeUtils.compareIds(idsA, idsB)).to.equal(true);
86 | });
87 | });
88 |
89 | describe('when arrays have some (not all) same values with duplicates', () => {
90 | beforeEach(() => {
91 | idsA = [1, 2, 3];
92 | idsB = [2, 3, 1, 1, 1, 1, 2, 4];
93 | });
94 |
95 | it('returns false', () => {
96 | expect(moleculeUtils.compareIds(idsA, idsB)).to.equal(false);
97 | });
98 | });
99 |
100 | describe('when B has some (not all) same values with duplicates with A', () => {
101 | beforeEach(() => {
102 | idsA = [2, 3, 1, 1, 1, 1, 2, 4];
103 | idsB = [1, 2, 3];
104 | });
105 |
106 | it('returns false', () => {
107 | expect(moleculeUtils.compareIds(idsA, idsB)).to.equal(false);
108 | });
109 | });
110 | });
111 |
112 | describe('updateObjectInPlace', () => {
113 | let oldObject;
114 | let newObject;
115 |
116 | describe('when given a flat object', () => {
117 | beforeEach(() => {
118 | oldObject = {
119 | one: 1,
120 | two: 2,
121 | };
122 | newObject = {
123 | one: 33,
124 | two: 'fdsa',
125 | four: 'what?',
126 | };
127 | });
128 |
129 | it('returns the same object but with newObject\'s data', () => {
130 | const result = moleculeUtils.updateObjectInPlace(oldObject, newObject);
131 | expect(result).to.equal(oldObject);
132 | expect(result.one).to.equal(newObject.one);
133 | expect(result.two).to.equal(newObject.two);
134 | expect(result.four).to.equal(newObject.four);
135 | });
136 | });
137 |
138 | describe('when given an object with a nested object', () => {
139 | beforeEach(() => {
140 | oldObject = {
141 | one: 1,
142 | obj: {
143 | two: 2,
144 | },
145 | };
146 | newObject = {
147 | one: 33,
148 | obj: {
149 | two: 22,
150 | },
151 | };
152 | });
153 |
154 | it('returns the same object but with newObject\'s data at the first level', () => {
155 | const result = moleculeUtils.updateObjectInPlace(oldObject, newObject);
156 | expect(result).to.equal(oldObject);
157 | expect(result.one).to.equal(newObject.one);
158 | });
159 |
160 | it('returns an object whose nested object is the same as oldObject\'s but with newObject\'s data', () => {
161 | const result = moleculeUtils.updateObjectInPlace(oldObject, newObject);
162 | expect(result.obj).to.equal(oldObject.obj);
163 | expect(result.obj.two).to.equal(newObject.obj.two);
164 | });
165 | });
166 |
167 | describe('when given a newObject that isn\'t an object', () => {
168 | beforeEach(() => {
169 | oldObject = {
170 | one: {},
171 | };
172 | newObject = {
173 | one: 'wait im not an object...',
174 | };
175 | });
176 |
177 | it('returns newObject', () => {
178 | const result = moleculeUtils.updateObjectInPlace(oldObject, newObject);
179 | expect(result).to.equal(oldObject);
180 | expect(result.one).to.equal(newObject.one);
181 | });
182 | });
183 | });
184 |
185 | describe('updateModels', () => {
186 | let oldModels;
187 | let newModels;
188 |
189 | describe('when given mismatching arrays', () => {
190 | beforeEach(() => {
191 | oldModels = [];
192 | newModels = [
193 | { id: 0 },
194 | ];
195 | });
196 |
197 | it('returns newModels', () => {
198 | const result = moleculeUtils.updateModels(oldModels, newModels);
199 | expect(result).to.equal(newModels);
200 | expect(result[0]).to.equal(newModels[0]);
201 | });
202 | });
203 |
204 | describe('when given an existing element', () => {
205 | beforeEach(() => {
206 | oldModels = [
207 | { id: 0 },
208 | ];
209 | newModels = [
210 | { id: 0, something: true },
211 | ];
212 | });
213 |
214 | it('returns oldModels with that element modified with new data', () => {
215 | const result = moleculeUtils.updateModels(oldModels, newModels);
216 | expect(result).to.equal(oldModels);
217 | expect(result[0]).to.equal(oldModels[0]);
218 | expect(result[0].something).to.equal(newModels[0].something);
219 | });
220 | });
221 | });
222 | });
223 |
--------------------------------------------------------------------------------
/example/css/main.css:
--------------------------------------------------------------------------------
1 | /*! HTML5 Boilerplate v5.3.0 | MIT License | https://html5boilerplate.com/ */
2 |
3 | /*
4 | * What follows is the result of much research on cross-browser styling.
5 | * Credit left inline and big thanks to Nicolas Gallagher, Jonathan Neal,
6 | * Kroc Camen, and the H5BP dev community and team.
7 | */
8 |
9 | /* ==========================================================================
10 | Base styles: opinionated defaults
11 | ========================================================================== */
12 |
13 | html {
14 | color: #222;
15 | font-size: 1em;
16 | line-height: 1.4;
17 | }
18 |
19 | /*
20 | * Remove text-shadow in selection highlight:
21 | * https://twitter.com/miketaylr/status/12228805301
22 | *
23 | * These selection rule sets have to be separate.
24 | * Customize the background color to match your design.
25 | */
26 |
27 | ::-moz-selection {
28 | background: #b3d4fc;
29 | text-shadow: none;
30 | }
31 |
32 | ::selection {
33 | background: #b3d4fc;
34 | text-shadow: none;
35 | }
36 |
37 | /*
38 | * A better looking default horizontal rule
39 | */
40 |
41 | hr {
42 | display: block;
43 | height: 1px;
44 | border: 0;
45 | border-top: 1px solid #ccc;
46 | margin: 1em 0;
47 | padding: 0;
48 | }
49 |
50 | /*
51 | * Remove the gap between audio, canvas, iframes,
52 | * images, videos and the bottom of their containers:
53 | * https://github.com/h5bp/html5-boilerplate/issues/440
54 | */
55 |
56 | audio,
57 | canvas,
58 | iframe,
59 | img,
60 | svg,
61 | video {
62 | vertical-align: middle;
63 | }
64 |
65 | /*
66 | * Remove default fieldset styles.
67 | */
68 |
69 | fieldset {
70 | border: 0;
71 | margin: 0;
72 | padding: 0;
73 | }
74 |
75 | /*
76 | * Allow only vertical resizing of textareas.
77 | */
78 |
79 | textarea {
80 | resize: vertical;
81 | }
82 |
83 | /* ==========================================================================
84 | Browser Upgrade Prompt
85 | ========================================================================== */
86 |
87 | .browserupgrade {
88 | margin: 0.2em 0;
89 | background: #ccc;
90 | color: #000;
91 | padding: 0.2em 0;
92 | }
93 |
94 | /* ==========================================================================
95 | Author's custom styles
96 | ========================================================================== */
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 | /* ==========================================================================
115 | Helper classes
116 | ========================================================================== */
117 |
118 | /*
119 | * Hide visually and from screen readers
120 | */
121 |
122 | .hidden {
123 | display: none !important;
124 | }
125 |
126 | /*
127 | * Hide only visually, but have it available for screen readers:
128 | * http://snook.ca/archives/html_and_css/hiding-content-for-accessibility
129 | */
130 |
131 | .visuallyhidden {
132 | border: 0;
133 | clip: rect(0 0 0 0);
134 | height: 1px;
135 | margin: -1px;
136 | overflow: hidden;
137 | padding: 0;
138 | position: absolute;
139 | width: 1px;
140 | }
141 |
142 | /*
143 | * Extends the .visuallyhidden class to allow the element
144 | * to be focusable when navigated to via the keyboard:
145 | * https://www.drupal.org/node/897638
146 | */
147 |
148 | .visuallyhidden.focusable:active,
149 | .visuallyhidden.focusable:focus {
150 | clip: auto;
151 | height: auto;
152 | margin: 0;
153 | overflow: visible;
154 | position: static;
155 | width: auto;
156 | }
157 |
158 | /*
159 | * Hide visually and from screen readers, but maintain layout
160 | */
161 |
162 | .invisible {
163 | visibility: hidden;
164 | }
165 |
166 | /*
167 | * Clearfix: contain floats
168 | *
169 | * For modern browsers
170 | * 1. The space content is one way to avoid an Opera bug when the
171 | * `contenteditable` attribute is included anywhere else in the document.
172 | * Otherwise it causes space to appear at the top and bottom of elements
173 | * that receive the `clearfix` class.
174 | * 2. The use of `table` rather than `block` is only necessary if using
175 | * `:before` to contain the top-margins of child elements.
176 | */
177 |
178 | .clearfix:before,
179 | .clearfix:after {
180 | content: " "; /* 1 */
181 | display: table; /* 2 */
182 | }
183 |
184 | .clearfix:after {
185 | clear: both;
186 | }
187 |
188 | /* ==========================================================================
189 | EXAMPLE Media Queries for Responsive Design.
190 | These examples override the primary ('mobile first') styles.
191 | Modify as content requires.
192 | ========================================================================== */
193 |
194 | @media only screen and (min-width: 35em) {
195 | /* Style adjustments for viewports that meet the condition */
196 | }
197 |
198 | @media print,
199 | (-webkit-min-device-pixel-ratio: 1.25),
200 | (min-resolution: 1.25dppx),
201 | (min-resolution: 120dpi) {
202 | /* Style adjustments for high resolution devices */
203 | }
204 |
205 | /* ==========================================================================
206 | Print styles.
207 | Inlined to avoid the additional HTTP request:
208 | http://www.phpied.com/delay-loading-your-print-css/
209 | ========================================================================== */
210 |
211 | @media print {
212 | *,
213 | *:before,
214 | *:after,
215 | *:first-letter,
216 | *:first-line {
217 | background: transparent !important;
218 | color: #000 !important; /* Black prints faster:
219 | http://www.sanbeiji.com/archives/953 */
220 | box-shadow: none !important;
221 | text-shadow: none !important;
222 | }
223 |
224 | a,
225 | a:visited {
226 | text-decoration: underline;
227 | }
228 |
229 | a[href]:after {
230 | content: " (" attr(href) ")";
231 | }
232 |
233 | abbr[title]:after {
234 | content: " (" attr(title) ")";
235 | }
236 |
237 | /*
238 | * Don't show links that are fragment identifiers,
239 | * or use the `javascript:` pseudo protocol
240 | */
241 |
242 | a[href^="#"]:after,
243 | a[href^="javascript:"]:after {
244 | content: "";
245 | }
246 |
247 | pre,
248 | blockquote {
249 | border: 1px solid #999;
250 | page-break-inside: avoid;
251 | }
252 |
253 | /*
254 | * Printing Tables:
255 | * http://css-discuss.incutio.com/wiki/Printing_Tables
256 | */
257 |
258 | thead {
259 | display: table-header-group;
260 | }
261 |
262 | tr,
263 | img {
264 | page-break-inside: avoid;
265 | }
266 |
267 | img {
268 | max-width: 100% !important;
269 | }
270 |
271 | p,
272 | h2,
273 | h3 {
274 | orphans: 3;
275 | widows: 3;
276 | }
277 |
278 | h2,
279 | h3 {
280 | page-break-after: avoid;
281 | }
282 | }
283 |
--------------------------------------------------------------------------------
/example/css/normalize.css:
--------------------------------------------------------------------------------
1 | /*! normalize.css v3.0.3 | MIT License | github.com/necolas/normalize.css */
2 |
3 | /**
4 | * 1. Set default font family to sans-serif.
5 | * 2. Prevent iOS and IE text size adjust after device orientation change,
6 | * without disabling user zoom.
7 | */
8 |
9 | html {
10 | font-family: sans-serif; /* 1 */
11 | -ms-text-size-adjust: 100%; /* 2 */
12 | -webkit-text-size-adjust: 100%; /* 2 */
13 | }
14 |
15 | /**
16 | * Remove default margin.
17 | */
18 |
19 | body {
20 | margin: 0;
21 | }
22 |
23 | /* HTML5 display definitions
24 | ========================================================================== */
25 |
26 | /**
27 | * Correct `block` display not defined for any HTML5 element in IE 8/9.
28 | * Correct `block` display not defined for `details` or `summary` in IE 10/11
29 | * and Firefox.
30 | * Correct `block` display not defined for `main` in IE 11.
31 | */
32 |
33 | article,
34 | aside,
35 | details,
36 | figcaption,
37 | figure,
38 | footer,
39 | header,
40 | hgroup,
41 | main,
42 | menu,
43 | nav,
44 | section,
45 | summary {
46 | display: block;
47 | }
48 |
49 | /**
50 | * 1. Correct `inline-block` display not defined in IE 8/9.
51 | * 2. Normalize vertical alignment of `progress` in Chrome, Firefox, and Opera.
52 | */
53 |
54 | audio,
55 | canvas,
56 | progress,
57 | video {
58 | display: inline-block; /* 1 */
59 | vertical-align: baseline; /* 2 */
60 | }
61 |
62 | /**
63 | * Prevent modern browsers from displaying `audio` without controls.
64 | * Remove excess height in iOS 5 devices.
65 | */
66 |
67 | audio:not([controls]) {
68 | display: none;
69 | height: 0;
70 | }
71 |
72 | /**
73 | * Address `[hidden]` styling not present in IE 8/9/10.
74 | * Hide the `template` element in IE 8/9/10/11, Safari, and Firefox < 22.
75 | */
76 |
77 | [hidden],
78 | template {
79 | display: none;
80 | }
81 |
82 | /* Links
83 | ========================================================================== */
84 |
85 | /**
86 | * Remove the gray background color from active links in IE 10.
87 | */
88 |
89 | a {
90 | background-color: transparent;
91 | }
92 |
93 | /**
94 | * Improve readability of focused elements when they are also in an
95 | * active/hover state.
96 | */
97 |
98 | a:active,
99 | a:hover {
100 | outline: 0;
101 | }
102 |
103 | /* Text-level semantics
104 | ========================================================================== */
105 |
106 | /**
107 | * Address styling not present in IE 8/9/10/11, Safari, and Chrome.
108 | */
109 |
110 | abbr[title] {
111 | border-bottom: 1px dotted;
112 | }
113 |
114 | /**
115 | * Address style set to `bolder` in Firefox 4+, Safari, and Chrome.
116 | */
117 |
118 | b,
119 | strong {
120 | font-weight: bold;
121 | }
122 |
123 | /**
124 | * Address styling not present in Safari and Chrome.
125 | */
126 |
127 | dfn {
128 | font-style: italic;
129 | }
130 |
131 | /**
132 | * Address variable `h1` font-size and margin within `section` and `article`
133 | * contexts in Firefox 4+, Safari, and Chrome.
134 | */
135 |
136 | h1 {
137 | font-size: 2em;
138 | margin: 0.67em 0;
139 | }
140 |
141 | /**
142 | * Address styling not present in IE 8/9.
143 | */
144 |
145 | mark {
146 | background: #ff0;
147 | color: #000;
148 | }
149 |
150 | /**
151 | * Address inconsistent and variable font size in all browsers.
152 | */
153 |
154 | small {
155 | font-size: 80%;
156 | }
157 |
158 | /**
159 | * Prevent `sub` and `sup` affecting `line-height` in all browsers.
160 | */
161 |
162 | sub,
163 | sup {
164 | font-size: 75%;
165 | line-height: 0;
166 | position: relative;
167 | vertical-align: baseline;
168 | }
169 |
170 | sup {
171 | top: -0.5em;
172 | }
173 |
174 | sub {
175 | bottom: -0.25em;
176 | }
177 |
178 | /* Embedded content
179 | ========================================================================== */
180 |
181 | /**
182 | * Remove border when inside `a` element in IE 8/9/10.
183 | */
184 |
185 | img {
186 | border: 0;
187 | }
188 |
189 | /**
190 | * Correct overflow not hidden in IE 9/10/11.
191 | */
192 |
193 | svg:not(:root) {
194 | overflow: hidden;
195 | }
196 |
197 | /* Grouping content
198 | ========================================================================== */
199 |
200 | /**
201 | * Address margin not present in IE 8/9 and Safari.
202 | */
203 |
204 | figure {
205 | margin: 1em 40px;
206 | }
207 |
208 | /**
209 | * Address differences between Firefox and other browsers.
210 | */
211 |
212 | hr {
213 | box-sizing: content-box;
214 | height: 0;
215 | }
216 |
217 | /**
218 | * Contain overflow in all browsers.
219 | */
220 |
221 | pre {
222 | overflow: auto;
223 | }
224 |
225 | /**
226 | * Address odd `em`-unit font size rendering in all browsers.
227 | */
228 |
229 | code,
230 | kbd,
231 | pre,
232 | samp {
233 | font-family: monospace, monospace;
234 | font-size: 1em;
235 | }
236 |
237 | /* Forms
238 | ========================================================================== */
239 |
240 | /**
241 | * Known limitation: by default, Chrome and Safari on OS X allow very limited
242 | * styling of `select`, unless a `border` property is set.
243 | */
244 |
245 | /**
246 | * 1. Correct color not being inherited.
247 | * Known issue: affects color of disabled elements.
248 | * 2. Correct font properties not being inherited.
249 | * 3. Address margins set differently in Firefox 4+, Safari, and Chrome.
250 | */
251 |
252 | button,
253 | input,
254 | optgroup,
255 | select,
256 | textarea {
257 | color: inherit; /* 1 */
258 | font: inherit; /* 2 */
259 | margin: 0; /* 3 */
260 | }
261 |
262 | /**
263 | * Address `overflow` set to `hidden` in IE 8/9/10/11.
264 | */
265 |
266 | button {
267 | overflow: visible;
268 | }
269 |
270 | /**
271 | * Address inconsistent `text-transform` inheritance for `button` and `select`.
272 | * All other form control elements do not inherit `text-transform` values.
273 | * Correct `button` style inheritance in Firefox, IE 8/9/10/11, and Opera.
274 | * Correct `select` style inheritance in Firefox.
275 | */
276 |
277 | button,
278 | select {
279 | text-transform: none;
280 | }
281 |
282 | /**
283 | * 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio`
284 | * and `video` controls.
285 | * 2. Correct inability to style clickable `input` types in iOS.
286 | * 3. Improve usability and consistency of cursor style between image-type
287 | * `input` and others.
288 | */
289 |
290 | button,
291 | html input[type="button"], /* 1 */
292 | input[type="reset"],
293 | input[type="submit"] {
294 | -webkit-appearance: button; /* 2 */
295 | cursor: pointer; /* 3 */
296 | }
297 |
298 | /**
299 | * Re-set default cursor for disabled elements.
300 | */
301 |
302 | button[disabled],
303 | html input[disabled] {
304 | cursor: default;
305 | }
306 |
307 | /**
308 | * Remove inner padding and border in Firefox 4+.
309 | */
310 |
311 | button::-moz-focus-inner,
312 | input::-moz-focus-inner {
313 | border: 0;
314 | padding: 0;
315 | }
316 |
317 | /**
318 | * Address Firefox 4+ setting `line-height` on `input` using `!important` in
319 | * the UA stylesheet.
320 | */
321 |
322 | input {
323 | line-height: normal;
324 | }
325 |
326 | /**
327 | * It's recommended that you don't attempt to style these elements.
328 | * Firefox's implementation doesn't respect box-sizing, padding, or width.
329 | *
330 | * 1. Address box sizing set to `content-box` in IE 8/9/10.
331 | * 2. Remove excess padding in IE 8/9/10.
332 | */
333 |
334 | input[type="checkbox"],
335 | input[type="radio"] {
336 | box-sizing: border-box; /* 1 */
337 | padding: 0; /* 2 */
338 | }
339 |
340 | /**
341 | * Fix the cursor style for Chrome's increment/decrement buttons. For certain
342 | * `font-size` values of the `input`, it causes the cursor style of the
343 | * decrement button to change from `default` to `text`.
344 | */
345 |
346 | input[type="number"]::-webkit-inner-spin-button,
347 | input[type="number"]::-webkit-outer-spin-button {
348 | height: auto;
349 | }
350 |
351 | /**
352 | * 1. Address `appearance` set to `searchfield` in Safari and Chrome.
353 | * 2. Address `box-sizing` set to `border-box` in Safari and Chrome.
354 | */
355 |
356 | input[type="search"] {
357 | -webkit-appearance: textfield; /* 1 */
358 | box-sizing: content-box; /* 2 */
359 | }
360 |
361 | /**
362 | * Remove inner padding and search cancel button in Safari and Chrome on OS X.
363 | * Safari (but not Chrome) clips the cancel button when the search input has
364 | * padding (and `textfield` appearance).
365 | */
366 |
367 | input[type="search"]::-webkit-search-cancel-button,
368 | input[type="search"]::-webkit-search-decoration {
369 | -webkit-appearance: none;
370 | }
371 |
372 | /**
373 | * Define consistent border, margin, and padding.
374 | */
375 |
376 | fieldset {
377 | border: 1px solid #c0c0c0;
378 | margin: 0 2px;
379 | padding: 0.35em 0.625em 0.75em;
380 | }
381 |
382 | /**
383 | * 1. Correct `color` not being inherited in IE 8/9/10/11.
384 | * 2. Remove padding so people aren't caught out if they zero out fieldsets.
385 | */
386 |
387 | legend {
388 | border: 0; /* 1 */
389 | padding: 0; /* 2 */
390 | }
391 |
392 | /**
393 | * Remove default vertical scrollbar in IE 8/9/10/11.
394 | */
395 |
396 | textarea {
397 | overflow: auto;
398 | }
399 |
400 | /**
401 | * Don't inherit the `font-weight` (applied by a rule above).
402 | * NOTE: the default cannot safely be changed in Chrome and Safari on OS X.
403 | */
404 |
405 | optgroup {
406 | font-weight: bold;
407 | }
408 |
409 | /* Tables
410 | ========================================================================== */
411 |
412 | /**
413 | * Remove most spacing between table cells.
414 | */
415 |
416 | table {
417 | border-collapse: collapse;
418 | border-spacing: 0;
419 | }
420 |
421 | td,
422 | th {
423 | padding: 0;
424 | }
425 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "[]"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright [yyyy] [name of copyright owner]
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/example/.htaccess:
--------------------------------------------------------------------------------
1 | # Apache Server Configs v2.14.0 | MIT License
2 | # https://github.com/h5bp/server-configs-apache
3 |
4 | # (!) Using `.htaccess` files slows down Apache, therefore, if you have
5 | # access to the main server configuration file (which is usually called
6 | # `httpd.conf`), you should add this logic there.
7 | #
8 | # https://httpd.apache.org/docs/current/howto/htaccess.html.
9 |
10 | # ######################################################################
11 | # # CROSS-ORIGIN #
12 | # ######################################################################
13 |
14 | # ----------------------------------------------------------------------
15 | # | Cross-origin requests |
16 | # ----------------------------------------------------------------------
17 |
18 | # Allow cross-origin requests.
19 | #
20 | # https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS
21 | # http://enable-cors.org/
22 | # http://www.w3.org/TR/cors/
23 |
24 | #
25 | # Header set Access-Control-Allow-Origin "*"
26 | #
27 |
28 | # ----------------------------------------------------------------------
29 | # | Cross-origin images |
30 | # ----------------------------------------------------------------------
31 |
32 | # Send the CORS header for images when browsers request it.
33 | #
34 | # https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_enabled_image
35 | # https://blog.chromium.org/2011/07/using-cross-domain-images-in-webgl-and.html
36 |
37 |
38 |
39 |
40 | SetEnvIf Origin ":" IS_CORS
41 | Header set Access-Control-Allow-Origin "*" env=IS_CORS
42 |
43 |
44 |
45 |
46 | # ----------------------------------------------------------------------
47 | # | Cross-origin web fonts |
48 | # ----------------------------------------------------------------------
49 |
50 | # Allow cross-origin access to web fonts.
51 |
52 |
53 |
54 | Header set Access-Control-Allow-Origin "*"
55 |
56 |
57 |
58 | # ----------------------------------------------------------------------
59 | # | Cross-origin resource timing |
60 | # ----------------------------------------------------------------------
61 |
62 | # Allow cross-origin access to the timing information for all resources.
63 | #
64 | # If a resource isn't served with a `Timing-Allow-Origin` header that
65 | # would allow its timing information to be shared with the document,
66 | # some of the attributes of the `PerformanceResourceTiming` object will
67 | # be set to zero.
68 | #
69 | # http://www.w3.org/TR/resource-timing/
70 | # http://www.stevesouders.com/blog/2014/08/21/resource-timing-practical-tips/
71 |
72 | #
73 | # Header set Timing-Allow-Origin: "*"
74 | #
75 |
76 |
77 | # ######################################################################
78 | # # ERRORS #
79 | # ######################################################################
80 |
81 | # ----------------------------------------------------------------------
82 | # | Custom error messages/pages |
83 | # ----------------------------------------------------------------------
84 |
85 | # Customize what Apache returns to the client in case of an error.
86 | # https://httpd.apache.org/docs/current/mod/core.html#errordocument
87 |
88 | ErrorDocument 404 /404.html
89 |
90 | # ----------------------------------------------------------------------
91 | # | Error prevention |
92 | # ----------------------------------------------------------------------
93 |
94 | # Disable the pattern matching based on filenames.
95 | #
96 | # This setting prevents Apache from returning a 404 error as the result
97 | # of a rewrite when the directory with the same name does not exist.
98 | #
99 | # https://httpd.apache.org/docs/current/content-negotiation.html#multiviews
100 |
101 | Options -MultiViews
102 |
103 |
104 | # ######################################################################
105 | # # INTERNET EXPLORER #
106 | # ######################################################################
107 |
108 | # ----------------------------------------------------------------------
109 | # | Document modes |
110 | # ----------------------------------------------------------------------
111 |
112 | # Force Internet Explorer 8/9/10 to render pages in the highest mode
113 | # available in the various cases when it may not.
114 | #
115 | # https://hsivonen.fi/doctype/#ie8
116 | #
117 | # (!) Starting with Internet Explorer 11, document modes are deprecated.
118 | # If your business still relies on older web apps and services that were
119 | # designed for older versions of Internet Explorer, you might want to
120 | # consider enabling `Enterprise Mode` throughout your company.
121 | #
122 | # https://msdn.microsoft.com/en-us/library/ie/bg182625.aspx#docmode
123 | # http://blogs.msdn.com/b/ie/archive/2014/04/02/stay-up-to-date-with-enterprise-mode-for-internet-explorer-11.aspx
124 |
125 |
126 |
127 | Header set X-UA-Compatible "IE=edge"
128 |
129 | # `mod_headers` cannot match based on the content-type, however,
130 | # the `X-UA-Compatible` response header should be send only for
131 | # HTML documents and not for the other resources.
132 |
133 |
134 | Header unset X-UA-Compatible
135 |
136 |
137 |
138 |
139 | # ----------------------------------------------------------------------
140 | # | Iframes cookies |
141 | # ----------------------------------------------------------------------
142 |
143 | # Allow cookies to be set from iframes in Internet Explorer.
144 | #
145 | # https://msdn.microsoft.com/en-us/library/ms537343.aspx
146 | # http://www.w3.org/TR/2000/CR-P3P-20001215/
147 |
148 | #
149 | # Header set P3P "policyref=\"/w3c/p3p.xml\", CP=\"IDC DSP COR ADM DEVi TAIi PSA PSD IVAi IVDi CONi HIS OUR IND CNT\""
150 | #
151 |
152 |
153 | # ######################################################################
154 | # # MEDIA TYPES AND CHARACTER ENCODINGS #
155 | # ######################################################################
156 |
157 | # ----------------------------------------------------------------------
158 | # | Media types |
159 | # ----------------------------------------------------------------------
160 |
161 | # Serve resources with the proper media types (f.k.a. MIME types).
162 | #
163 | # https://www.iana.org/assignments/media-types/media-types.xhtml
164 | # https://httpd.apache.org/docs/current/mod/mod_mime.html#addtype
165 |
166 |
167 |
168 | # Data interchange
169 |
170 | AddType application/atom+xml atom
171 | AddType application/json json map topojson
172 | AddType application/ld+json jsonld
173 | AddType application/rss+xml rss
174 | AddType application/vnd.geo+json geojson
175 | AddType application/xml rdf xml
176 |
177 |
178 | # JavaScript
179 |
180 | # Normalize to standard type.
181 | # https://tools.ietf.org/html/rfc4329#section-7.2
182 |
183 | AddType application/javascript js
184 |
185 |
186 | # Manifest files
187 |
188 | AddType application/manifest+json webmanifest
189 | AddType application/x-web-app-manifest+json webapp
190 | AddType text/cache-manifest appcache
191 |
192 |
193 | # Media files
194 |
195 | AddType audio/mp4 f4a f4b m4a
196 | AddType audio/ogg oga ogg opus
197 | AddType image/bmp bmp
198 | AddType image/svg+xml svg svgz
199 | AddType image/webp webp
200 | AddType video/mp4 f4v f4p m4v mp4
201 | AddType video/ogg ogv
202 | AddType video/webm webm
203 | AddType video/x-flv flv
204 |
205 | # Serving `.ico` image files with a different media type
206 | # prevents Internet Explorer from displaying then as images:
207 | # https://github.com/h5bp/html5-boilerplate/commit/37b5fec090d00f38de64b591bcddcb205aadf8ee
208 |
209 | AddType image/x-icon cur ico
210 |
211 |
212 | # Web fonts
213 |
214 | AddType application/font-woff woff
215 | AddType application/font-woff2 woff2
216 | AddType application/vnd.ms-fontobject eot
217 |
218 | # Browsers usually ignore the font media types and simply sniff
219 | # the bytes to figure out the font type.
220 | # https://mimesniff.spec.whatwg.org/#matching-a-font-type-pattern
221 | #
222 | # However, Blink and WebKit based browsers will show a warning
223 | # in the console if the following font types are served with any
224 | # other media types.
225 |
226 | AddType application/x-font-ttf ttc ttf
227 | AddType font/opentype otf
228 |
229 |
230 | # Other
231 |
232 | AddType application/octet-stream safariextz
233 | AddType application/x-bb-appworld bbaw
234 | AddType application/x-chrome-extension crx
235 | AddType application/x-opera-extension oex
236 | AddType application/x-xpinstall xpi
237 | AddType text/vcard vcard vcf
238 | AddType text/vnd.rim.location.xloc xloc
239 | AddType text/vtt vtt
240 | AddType text/x-component htc
241 |
242 |
243 |
244 | # ----------------------------------------------------------------------
245 | # | Character encodings |
246 | # ----------------------------------------------------------------------
247 |
248 | # Serve all resources labeled as `text/html` or `text/plain`
249 | # with the media type `charset` parameter set to `UTF-8`.
250 | #
251 | # https://httpd.apache.org/docs/current/mod/core.html#adddefaultcharset
252 |
253 | AddDefaultCharset utf-8
254 |
255 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
256 |
257 | # Serve the following file types with the media type `charset`
258 | # parameter set to `UTF-8`.
259 | #
260 | # https://httpd.apache.org/docs/current/mod/mod_mime.html#addcharset
261 |
262 |
263 | AddCharset utf-8 .atom \
264 | .bbaw \
265 | .css \
266 | .geojson \
267 | .js \
268 | .json \
269 | .jsonld \
270 | .manifest \
271 | .rdf \
272 | .rss \
273 | .topojson \
274 | .vtt \
275 | .webapp \
276 | .webmanifest \
277 | .xloc \
278 | .xml
279 |
280 |
281 |
282 | # ######################################################################
283 | # # REWRITES #
284 | # ######################################################################
285 |
286 | # ----------------------------------------------------------------------
287 | # | Rewrite engine |
288 | # ----------------------------------------------------------------------
289 |
290 | # (1) Turn on the rewrite engine (this is necessary in order for
291 | # the `RewriteRule` directives to work).
292 | #
293 | # https://httpd.apache.org/docs/current/mod/mod_rewrite.html#RewriteEngine
294 | #
295 | # (2) Enable the `FollowSymLinks` option if it isn't already.
296 | #
297 | # https://httpd.apache.org/docs/current/mod/core.html#options
298 | #
299 | # (3) If your web host doesn't allow the `FollowSymlinks` option,
300 | # you need to comment it out or remove it, and then uncomment
301 | # the `Options +SymLinksIfOwnerMatch` line (4), but be aware
302 | # of the performance impact.
303 | #
304 | # https://httpd.apache.org/docs/current/misc/perf-tuning.html#symlinks
305 | #
306 | # (4) Some cloud hosting services will require you set `RewriteBase`.
307 | #
308 | # https://www.rackspace.com/knowledge_center/frequently-asked-question/why-is-modrewrite-not-working-on-my-site
309 | # https://httpd.apache.org/docs/current/mod/mod_rewrite.html#rewritebase
310 | #
311 | # (5) Depending on how your server is set up, you may also need to
312 | # use the `RewriteOptions` directive to enable some options for
313 | # the rewrite engine.
314 | #
315 | # https://httpd.apache.org/docs/current/mod/mod_rewrite.html#rewriteoptions
316 | #
317 | # (6) Set %{ENV:PROTO} variable, to allow rewrites to redirect with the
318 | # appropriate schema automatically (http or https).
319 |
320 |
321 |
322 | # (1)
323 | RewriteEngine On
324 |
325 | # (2)
326 | Options +FollowSymlinks
327 |
328 | # (3)
329 | # Options +SymLinksIfOwnerMatch
330 |
331 | # (4)
332 | # RewriteBase /
333 |
334 | # (5)
335 | # RewriteOptions
336 |
337 | # (6)
338 | RewriteCond %{HTTPS} =on
339 | RewriteRule ^ - [env=proto:https]
340 | RewriteCond %{HTTPS} !=on
341 | RewriteRule ^ - [env=proto:http]
342 |
343 |
344 |
345 | # ----------------------------------------------------------------------
346 | # | Forcing `https://` |
347 | # ----------------------------------------------------------------------
348 |
349 | # Redirect from the `http://` to the `https://` version of the URL.
350 | # https://wiki.apache.org/httpd/RewriteHTTPToHTTPS
351 |
352 | #
353 | # RewriteEngine On
354 | # RewriteCond %{HTTPS} !=on
355 | # RewriteRule ^(.*)$ https://%{HTTP_HOST}/$1 [R=301,L]
356 | #
357 |
358 | # ----------------------------------------------------------------------
359 | # | Suppressing / Forcing the `www.` at the beginning of URLs |
360 | # ----------------------------------------------------------------------
361 |
362 | # The same content should never be available under two different
363 | # URLs, especially not with and without `www.` at the beginning.
364 | # This can cause SEO problems (duplicate content), and therefore,
365 | # you should choose one of the alternatives and redirect the other
366 | # one.
367 | #
368 | # By default `Option 1` (no `www.`) is activated.
369 | # http://no-www.org/faq.php?q=class_b
370 | #
371 | # If you would prefer to use `Option 2`, just comment out all the
372 | # lines from `Option 1` and uncomment the ones from `Option 2`.
373 | #
374 | # (!) NEVER USE BOTH RULES AT THE SAME TIME!
375 |
376 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
377 |
378 | # Option 1: rewrite www.example.com → example.com
379 |
380 |
381 | RewriteEngine On
382 | RewriteCond %{HTTPS} !=on
383 | RewriteCond %{HTTP_HOST} ^www\.(.+)$ [NC]
384 | RewriteRule ^ %{ENV:PROTO}://%1%{REQUEST_URI} [R=301,L]
385 |
386 |
387 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
388 |
389 | # Option 2: rewrite example.com → www.example.com
390 | #
391 | # Be aware that the following might not be a good idea if you use "real"
392 | # subdomains for certain parts of your website.
393 |
394 | #
395 | # RewriteEngine On
396 | # RewriteCond %{HTTPS} !=on
397 | # RewriteCond %{HTTP_HOST} !^www\. [NC]
398 | # RewriteCond %{SERVER_ADDR} !=127.0.0.1
399 | # RewriteCond %{SERVER_ADDR} !=::1
400 | # RewriteRule ^ %{ENV:PROTO}://www.%{HTTP_HOST}%{REQUEST_URI} [R=301,L]
401 | #
402 |
403 |
404 | # ######################################################################
405 | # # SECURITY #
406 | # ######################################################################
407 |
408 | # ----------------------------------------------------------------------
409 | # | Clickjacking |
410 | # ----------------------------------------------------------------------
411 |
412 | # Protect website against clickjacking.
413 | #
414 | # The example below sends the `X-Frame-Options` response header with
415 | # the value `DENY`, informing browsers not to display the content of
416 | # the web page in any frame.
417 | #
418 | # This might not be the best setting for everyone. You should read
419 | # about the other two possible values the `X-Frame-Options` header
420 | # field can have: `SAMEORIGIN` and `ALLOW-FROM`.
421 | # https://tools.ietf.org/html/rfc7034#section-2.1.
422 | #
423 | # Keep in mind that while you could send the `X-Frame-Options` header
424 | # for all of your website’s pages, this has the potential downside that
425 | # it forbids even non-malicious framing of your content (e.g.: when
426 | # users visit your website using a Google Image Search results page).
427 | #
428 | # Nonetheless, you should ensure that you send the `X-Frame-Options`
429 | # header for all pages that allow a user to make a state changing
430 | # operation (e.g: pages that contain one-click purchase links, checkout
431 | # or bank-transfer confirmation pages, pages that make permanent
432 | # configuration changes, etc.).
433 | #
434 | # Sending the `X-Frame-Options` header can also protect your website
435 | # against more than just clickjacking attacks:
436 | # https://cure53.de/xfo-clickjacking.pdf.
437 | #
438 | # https://tools.ietf.org/html/rfc7034
439 | # http://blogs.msdn.com/b/ieinternals/archive/2010/03/30/combating-clickjacking-with-x-frame-options.aspx
440 | # https://www.owasp.org/index.php/Clickjacking
441 |
442 | #
443 |
444 | # Header set X-Frame-Options "DENY"
445 |
446 | # # `mod_headers` cannot match based on the content-type, however,
447 | # # the `X-Frame-Options` response header should be send only for
448 | # # HTML documents and not for the other resources.
449 |
450 | #
451 | # Header unset X-Frame-Options
452 | #
453 |
454 | #
455 |
456 | # ----------------------------------------------------------------------
457 | # | Content Security Policy (CSP) |
458 | # ----------------------------------------------------------------------
459 |
460 | # Mitigate the risk of cross-site scripting and other content-injection
461 | # attacks.
462 | #
463 | # This can be done by setting a `Content Security Policy` which
464 | # whitelists trusted sources of content for your website.
465 | #
466 | # The example header below allows ONLY scripts that are loaded from
467 | # the current website's origin (no inline scripts, no CDN, etc).
468 | # That almost certainly won't work as-is for your website!
469 | #
470 | # To make things easier, you can use an online CSP header generator
471 | # such as: http://cspisawesome.com/.
472 | #
473 | # http://content-security-policy.com/
474 | # http://www.html5rocks.com/en/tutorials/security/content-security-policy/
475 | # http://www.w3.org/TR/CSP11/).
476 |
477 | #
478 |
479 | # Header set Content-Security-Policy "script-src 'self'; object-src 'self'"
480 |
481 | # # `mod_headers` cannot match based on the content-type, however,
482 | # # the `Content-Security-Policy` response header should be send
483 | # # only for HTML documents and not for the other resources.
484 |
485 | #
486 | # Header unset Content-Security-Policy
487 | #
488 |
489 | #
490 |
491 | # ----------------------------------------------------------------------
492 | # | File access |
493 | # ----------------------------------------------------------------------
494 |
495 | # Block access to directories without a default document.
496 | #
497 | # You should leave the following uncommented, as you shouldn't allow
498 | # anyone to surf through every directory on your server (which may
499 | # includes rather private places such as the CMS's directories).
500 |
501 |
502 | Options -Indexes
503 |
504 |
505 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
506 |
507 | # Block access to all hidden files and directories with the exception of
508 | # the visible content from within the `/.well-known/` hidden directory.
509 | #
510 | # These types of files usually contain user preferences or the preserved
511 | # state of an utility, and can include rather private places like, for
512 | # example, the `.git` or `.svn` directories.
513 | #
514 | # The `/.well-known/` directory represents the standard (RFC 5785) path
515 | # prefix for "well-known locations" (e.g.: `/.well-known/manifest.json`,
516 | # `/.well-known/keybase.txt`), and therefore, access to its visible
517 | # content should not be blocked.
518 | #
519 | # https://www.mnot.net/blog/2010/04/07/well-known
520 | # https://tools.ietf.org/html/rfc5785
521 |
522 |
523 | RewriteEngine On
524 | RewriteCond %{REQUEST_URI} "!(^|/)\.well-known/([^./]+./?)+$" [NC]
525 | RewriteCond %{SCRIPT_FILENAME} -d [OR]
526 | RewriteCond %{SCRIPT_FILENAME} -f
527 | RewriteRule "(^|/)\." - [F]
528 |
529 |
530 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
531 |
532 | # Block access to files that can expose sensitive information.
533 | #
534 | # By default, block access to backup and source files that may be
535 | # left by some text editors and can pose a security risk when anyone
536 | # has access to them.
537 | #
538 | # http://feross.org/cmsploit/
539 | #
540 | # (!) Update the `` regular expression from below to
541 | # include any files that might end up on your production server and
542 | # can expose sensitive information about your website. These files may
543 | # include: configuration files, files that contain metadata about the
544 | # project (e.g.: project dependencies), build scripts, etc..
545 |
546 |
547 |
548 | # Apache < 2.3
549 |
550 | Order allow,deny
551 | Deny from all
552 | Satisfy All
553 |
554 |
555 | # Apache ≥ 2.3
556 |
557 | Require all denied
558 |
559 |
560 |
561 |
562 | # ----------------------------------------------------------------------
563 | # | HTTP Strict Transport Security (HSTS) |
564 | # ----------------------------------------------------------------------
565 |
566 | # Force client-side SSL redirection.
567 | #
568 | # If a user types `example.com` in their browser, even if the server
569 | # redirects them to the secure version of the website, that still leaves
570 | # a window of opportunity (the initial HTTP connection) for an attacker
571 | # to downgrade or redirect the request.
572 | #
573 | # The following header ensures that browser will ONLY connect to your
574 | # server via HTTPS, regardless of what the users type in the browser's
575 | # address bar.
576 | #
577 | # (!) Remove the `includeSubDomains` optional directive if the website's
578 | # subdomains are not using HTTPS.
579 | #
580 | # http://www.html5rocks.com/en/tutorials/security/transport-layer-security/
581 | # https://tools.ietf.org/html/draft-ietf-websec-strict-transport-sec-14#section-6.1
582 | # http://blogs.msdn.com/b/ieinternals/archive/2014/08/18/hsts-strict-transport-security-attacks-mitigations-deployment-https.aspx
583 |
584 | #
585 | # Header always set Strict-Transport-Security "max-age=16070400; includeSubDomains"
586 | #
587 |
588 | # ----------------------------------------------------------------------
589 | # | Reducing MIME type security risks |
590 | # ----------------------------------------------------------------------
591 |
592 | # Prevent some browsers from MIME-sniffing the response.
593 | #
594 | # This reduces exposure to drive-by download attacks and cross-origin
595 | # data leaks, and should be left uncommented, especially if the server
596 | # is serving user-uploaded content or content that could potentially be
597 | # treated as executable by the browser.
598 | #
599 | # http://www.slideshare.net/hasegawayosuke/owasp-hasegawa
600 | # http://blogs.msdn.com/b/ie/archive/2008/07/02/ie8-security-part-v-comprehensive-protection.aspx
601 | # https://msdn.microsoft.com/en-us/library/ie/gg622941.aspx
602 | # https://mimesniff.spec.whatwg.org/
603 |
604 |
605 | Header set X-Content-Type-Options "nosniff"
606 |
607 |
608 | # ----------------------------------------------------------------------
609 | # | Reflected Cross-Site Scripting (XSS) attacks |
610 | # ----------------------------------------------------------------------
611 |
612 | # (1) Try to re-enable the cross-site scripting (XSS) filter built
613 | # into most web browsers.
614 | #
615 | # The filter is usually enabled by default, but in some cases it
616 | # may be disabled by the user. However, in Internet Explorer for
617 | # example, it can be re-enabled just by sending the
618 | # `X-XSS-Protection` header with the value of `1`.
619 | #
620 | # (2) Prevent web browsers from rendering the web page if a potential
621 | # reflected (a.k.a non-persistent) XSS attack is detected by the
622 | # filter.
623 | #
624 | # By default, if the filter is enabled and browsers detect a
625 | # reflected XSS attack, they will attempt to block the attack
626 | # by making the smallest possible modifications to the returned
627 | # web page.
628 | #
629 | # Unfortunately, in some browsers (e.g.: Internet Explorer),
630 | # this default behavior may allow the XSS filter to be exploited,
631 | # thereby, it's better to inform browsers to prevent the rendering
632 | # of the page altogether, instead of attempting to modify it.
633 | #
634 | # https://hackademix.net/2009/11/21/ies-xss-filter-creates-xss-vulnerabilities
635 | #
636 | # (!) Do not rely on the XSS filter to prevent XSS attacks! Ensure that
637 | # you are taking all possible measures to prevent XSS attacks, the
638 | # most obvious being: validating and sanitizing your website's inputs.
639 | #
640 | # http://blogs.msdn.com/b/ie/archive/2008/07/02/ie8-security-part-iv-the-xss-filter.aspx
641 | # http://blogs.msdn.com/b/ieinternals/archive/2011/01/31/controlling-the-internet-explorer-xss-filter-with-the-x-xss-protection-http-header.aspx
642 | # https://www.owasp.org/index.php/Cross-site_Scripting_%28XSS%29
643 |
644 | #
645 |
646 | # # (1) (2)
647 | # Header set X-XSS-Protection "1; mode=block"
648 |
649 | # # `mod_headers` cannot match based on the content-type, however,
650 | # # the `X-XSS-Protection` response header should be send only for
651 | # # HTML documents and not for the other resources.
652 |
653 | #
654 | # Header unset X-XSS-Protection
655 | #
656 |
657 | #
658 |
659 | # ----------------------------------------------------------------------
660 | # | Server-side technology information |
661 | # ----------------------------------------------------------------------
662 |
663 | # Remove the `X-Powered-By` response header that:
664 | #
665 | # * is set by some frameworks and server-side languages
666 | # (e.g.: ASP.NET, PHP), and its value contains information
667 | # about them (e.g.: their name, version number)
668 | #
669 | # * doesn't provide any value as far as users are concern,
670 | # and in some cases, the information provided by it can
671 | # be used by attackers
672 | #
673 | # (!) If you can, you should disable the `X-Powered-By` header from the
674 | # language / framework level (e.g.: for PHP, you can do that by setting
675 | # `expose_php = off` in `php.ini`)
676 | #
677 | # https://php.net/manual/en/ini.core.php#ini.expose-php
678 |
679 |
680 | Header unset X-Powered-By
681 |
682 |
683 | # ----------------------------------------------------------------------
684 | # | Server software information |
685 | # ----------------------------------------------------------------------
686 |
687 | # Prevent Apache from adding a trailing footer line containing
688 | # information about the server to the server-generated documents
689 | # (e.g.: error messages, directory listings, etc.)
690 | #
691 | # https://httpd.apache.org/docs/current/mod/core.html#serversignature
692 |
693 | ServerSignature Off
694 |
695 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
696 |
697 | # Prevent Apache from sending in the `Server` response header its
698 | # exact version number, the description of the generic OS-type or
699 | # information about its compiled-in modules.
700 | #
701 | # (!) The `ServerTokens` directive will only work in the main server
702 | # configuration file, so don't try to enable it in the `.htaccess` file!
703 | #
704 | # https://httpd.apache.org/docs/current/mod/core.html#servertokens
705 |
706 | #ServerTokens Prod
707 |
708 |
709 | # ######################################################################
710 | # # WEB PERFORMANCE #
711 | # ######################################################################
712 |
713 | # ----------------------------------------------------------------------
714 | # | Compression |
715 | # ----------------------------------------------------------------------
716 |
717 |
718 |
719 | # Force compression for mangled `Accept-Encoding` request headers
720 | # https://developer.yahoo.com/blogs/ydn/pushing-beyond-gzipping-25601.html
721 |
722 |
723 |
724 | SetEnvIfNoCase ^(Accept-EncodXng|X-cept-Encoding|X{15}|~{15}|-{15})$ ^((gzip|deflate)\s*,?\s*)+|[X~-]{4,13}$ HAVE_Accept-Encoding
725 | RequestHeader append Accept-Encoding "gzip,deflate" env=HAVE_Accept-Encoding
726 |
727 |
728 |
729 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
730 |
731 | # Compress all output labeled with one of the following media types.
732 | #
733 | # (!) For Apache versions below version 2.3.7 you don't need to
734 | # enable `mod_filter` and can remove the ``
735 | # and `` lines as `AddOutputFilterByType` is still in
736 | # the core directives.
737 | #
738 | # https://httpd.apache.org/docs/current/mod/mod_filter.html#addoutputfilterbytype
739 |
740 |
741 | AddOutputFilterByType DEFLATE "application/atom+xml" \
742 | "application/javascript" \
743 | "application/json" \
744 | "application/ld+json" \
745 | "application/manifest+json" \
746 | "application/rdf+xml" \
747 | "application/rss+xml" \
748 | "application/schema+json" \
749 | "application/vnd.geo+json" \
750 | "application/vnd.ms-fontobject" \
751 | "application/x-font-ttf" \
752 | "application/x-javascript" \
753 | "application/x-web-app-manifest+json" \
754 | "application/xhtml+xml" \
755 | "application/xml" \
756 | "font/eot" \
757 | "font/opentype" \
758 | "image/bmp" \
759 | "image/svg+xml" \
760 | "image/vnd.microsoft.icon" \
761 | "image/x-icon" \
762 | "text/cache-manifest" \
763 | "text/css" \
764 | "text/html" \
765 | "text/javascript" \
766 | "text/plain" \
767 | "text/vcard" \
768 | "text/vnd.rim.location.xloc" \
769 | "text/vtt" \
770 | "text/x-component" \
771 | "text/x-cross-domain-policy" \
772 | "text/xml"
773 |
774 |
775 |
776 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
777 |
778 | # Map the following filename extensions to the specified
779 | # encoding type in order to make Apache serve the file types
780 | # with the appropriate `Content-Encoding` response header
781 | # (do note that this will NOT make Apache compress them!).
782 | #
783 | # If these files types would be served without an appropriate
784 | # `Content-Enable` response header, client applications (e.g.:
785 | # browsers) wouldn't know that they first need to uncompress
786 | # the response, and thus, wouldn't be able to understand the
787 | # content.
788 | #
789 | # https://httpd.apache.org/docs/current/mod/mod_mime.html#addencoding
790 |
791 |
792 | AddEncoding gzip svgz
793 |
794 |
795 |
796 |
797 | # ----------------------------------------------------------------------
798 | # | Content transformation |
799 | # ----------------------------------------------------------------------
800 |
801 | # Prevent intermediate caches or proxies (e.g.: such as the ones
802 | # used by mobile network providers) from modifying the website's
803 | # content.
804 | #
805 | # https://tools.ietf.org/html/rfc2616#section-14.9.5
806 | #
807 | # (!) If you are using `mod_pagespeed`, please note that setting
808 | # the `Cache-Control: no-transform` response header will prevent
809 | # `PageSpeed` from rewriting `HTML` files, and, if the
810 | # `ModPagespeedDisableRewriteOnNoTransform` directive isn't set
811 | # to `off`, also from rewriting other resources.
812 | #
813 | # https://developers.google.com/speed/pagespeed/module/configuration#notransform
814 |
815 | #
816 | # Header merge Cache-Control "no-transform"
817 | #
818 |
819 | # ----------------------------------------------------------------------
820 | # | ETags |
821 | # ----------------------------------------------------------------------
822 |
823 | # Remove `ETags` as resources are sent with far-future expires headers.
824 | #
825 | # https://developer.yahoo.com/performance/rules.html#etags
826 | # https://tools.ietf.org/html/rfc7232#section-2.3
827 |
828 | # `FileETag None` doesn't work in all cases.
829 |
830 | Header unset ETag
831 |
832 |
833 | FileETag None
834 |
835 | # ----------------------------------------------------------------------
836 | # | Expires headers |
837 | # ----------------------------------------------------------------------
838 |
839 | # Serve resources with far-future expires headers.
840 | #
841 | # (!) If you don't control versioning with filename-based
842 | # cache busting, you should consider lowering the cache times
843 | # to something like one week.
844 | #
845 | # https://httpd.apache.org/docs/current/mod/mod_expires.html
846 |
847 |
848 |
849 | ExpiresActive on
850 | ExpiresDefault "access plus 1 month"
851 |
852 | # CSS
853 |
854 | ExpiresByType text/css "access plus 1 year"
855 |
856 |
857 | # Data interchange
858 |
859 | ExpiresByType application/atom+xml "access plus 1 hour"
860 | ExpiresByType application/rdf+xml "access plus 1 hour"
861 | ExpiresByType application/rss+xml "access plus 1 hour"
862 |
863 | ExpiresByType application/json "access plus 0 seconds"
864 | ExpiresByType application/ld+json "access plus 0 seconds"
865 | ExpiresByType application/schema+json "access plus 0 seconds"
866 | ExpiresByType application/vnd.geo+json "access plus 0 seconds"
867 | ExpiresByType application/xml "access plus 0 seconds"
868 | ExpiresByType text/xml "access plus 0 seconds"
869 |
870 |
871 | # Favicon (cannot be renamed!) and cursor images
872 |
873 | ExpiresByType image/vnd.microsoft.icon "access plus 1 week"
874 | ExpiresByType image/x-icon "access plus 1 week"
875 |
876 | # HTML
877 |
878 | ExpiresByType text/html "access plus 0 seconds"
879 |
880 |
881 | # JavaScript
882 |
883 | ExpiresByType application/javascript "access plus 1 year"
884 | ExpiresByType application/x-javascript "access plus 1 year"
885 | ExpiresByType text/javascript "access plus 1 year"
886 |
887 |
888 | # Manifest files
889 |
890 | ExpiresByType application/manifest+json "access plus 1 week"
891 | ExpiresByType application/x-web-app-manifest+json "access plus 0 seconds"
892 | ExpiresByType text/cache-manifest "access plus 0 seconds"
893 |
894 |
895 | # Media files
896 |
897 | ExpiresByType audio/ogg "access plus 1 month"
898 | ExpiresByType image/bmp "access plus 1 month"
899 | ExpiresByType image/gif "access plus 1 month"
900 | ExpiresByType image/jpeg "access plus 1 month"
901 | ExpiresByType image/png "access plus 1 month"
902 | ExpiresByType image/svg+xml "access plus 1 month"
903 | ExpiresByType image/webp "access plus 1 month"
904 | ExpiresByType video/mp4 "access plus 1 month"
905 | ExpiresByType video/ogg "access plus 1 month"
906 | ExpiresByType video/webm "access plus 1 month"
907 |
908 |
909 | # Web fonts
910 |
911 | # Embedded OpenType (EOT)
912 | ExpiresByType application/vnd.ms-fontobject "access plus 1 month"
913 | ExpiresByType font/eot "access plus 1 month"
914 |
915 | # OpenType
916 | ExpiresByType font/opentype "access plus 1 month"
917 |
918 | # TrueType
919 | ExpiresByType application/x-font-ttf "access plus 1 month"
920 |
921 | # Web Open Font Format (WOFF) 1.0
922 | ExpiresByType application/font-woff "access plus 1 month"
923 | ExpiresByType application/x-font-woff "access plus 1 month"
924 | ExpiresByType font/woff "access plus 1 month"
925 |
926 | # Web Open Font Format (WOFF) 2.0
927 | ExpiresByType application/font-woff2 "access plus 1 month"
928 |
929 |
930 | # Other
931 |
932 | ExpiresByType text/x-cross-domain-policy "access plus 1 week"
933 |
934 |
935 |
936 | # ----------------------------------------------------------------------
937 | # | File concatenation |
938 | # ----------------------------------------------------------------------
939 |
940 | # Allow concatenation from within specific files.
941 | #
942 | # e.g.:
943 | #
944 | # If you have the following lines in a file called, for
945 | # example, `main.combined.js`:
946 | #
947 | #
948 | #
949 | #
950 | # Apache will replace those lines with the content of the
951 | # specified files.
952 |
953 | #
954 | #
955 | # Options +Includes
956 | # AddOutputFilterByType INCLUDES application/javascript \
957 | # application/x-javascript \
958 | # text/javascript
959 | # SetOutputFilter INCLUDES
960 | #
961 | #
962 | # Options +Includes
963 | # AddOutputFilterByType INCLUDES text/css
964 | # SetOutputFilter INCLUDES
965 | #
966 | #
967 |
968 | # ----------------------------------------------------------------------
969 | # | Filename-based cache busting |
970 | # ----------------------------------------------------------------------
971 |
972 | # If you're not using a build process to manage your filename version
973 | # revving, you might want to consider enabling the following directives
974 | # to route all requests such as `/style.12345.css` to `/style.css`.
975 | #
976 | # To understand why this is important and even a better solution than
977 | # using something like `*.css?v231`, please see:
978 | # http://www.stevesouders.com/blog/2008/08/23/revving-filenames-dont-use-querystring/
979 |
980 | #
981 | # RewriteEngine On
982 | # RewriteCond %{REQUEST_FILENAME} !-f
983 | # RewriteRule ^(.+)\.(\d+)\.(bmp|css|cur|gif|ico|jpe?g|js|png|svgz?|webp|webmanifest)$ $1.$3 [L]
984 | #
985 |
--------------------------------------------------------------------------------